Inline Class
Intent
Section titled “Intent”Take a class that has shrunk to almost nothing — a couple of fields and a method or two that no longer justify a separate type — and merge it into the class that holds it. The behaviour is unchanged; you have simply removed a layer that stopped earning its keep.
The smell
Section titled “The smell”This is the inverse of Extract Class, and the cure for a class that has become a needless middle layer. The signs: a class with one field and a trivial method, a type that exists only to be wrapped by one caller, or a former responsibility that earlier refactorings have hollowed out. If reading the code means hopping to a tiny class and straight back, that hop is pure overhead.
Before → After
Section titled “Before → After”A TrackingInformation class that holds two fields and formats them, used by a single Shipment. Before, the data and its one method sit in a separate type. After, they fold into Shipment.
// Beforeclass TrackingInformation { constructor(public shippingCompany: string, public trackingNumber: string) {}
display(): string { return `${this.shippingCompany}: ${this.trackingNumber}`; }}
class Shipment { constructor(public trackingInfo: TrackingInformation) {}
status(): string { return `Shipped via ${this.trackingInfo.display()}`; }}
// Afterclass Shipment { constructor(public shippingCompany: string, public trackingNumber: string) {}
private trackingDisplay(): string { return `${this.shippingCompany}: ${this.trackingNumber}`; }
status(): string { return `Shipped via ${this.trackingDisplay()}`; }}# Beforeclass TrackingInformation: def __init__(self, shipping_company, tracking_number): self.shipping_company = shipping_company self.tracking_number = tracking_number
def display(self): return f"{self.shipping_company}: {self.tracking_number}"
class Shipment: def __init__(self, tracking_info): self.tracking_info = tracking_info
def status(self): return f"Shipped via {self.tracking_info.display()}"
# Afterclass Shipment: def __init__(self, shipping_company, tracking_number): self.shipping_company = shipping_company self.tracking_number = tracking_number
def _tracking_display(self): return f"{self.shipping_company}: {self.tracking_number}"
def status(self): return f"Shipped via {self._tracking_display()}"// Beforetype TrackingInformation struct { ShippingCompany string TrackingNumber string}
func (t TrackingInformation) Display() string { return fmt.Sprintf("%s: %s", t.ShippingCompany, t.TrackingNumber)}
type Shipment struct { TrackingInfo TrackingInformation}
func (s Shipment) Status() string { return fmt.Sprintf("Shipped via %s", s.TrackingInfo.Display())}
// Aftertype Shipment struct { ShippingCompany string TrackingNumber string}
func (s Shipment) trackingDisplay() string { return fmt.Sprintf("%s: %s", s.ShippingCompany, s.TrackingNumber)}
func (s Shipment) Status() string { return fmt.Sprintf("Shipped via %s", s.trackingDisplay())}// Beforestruct TrackingInformation { shipping_company: String, tracking_number: String,}
impl TrackingInformation { fn display(&self) -> String { format!("{}: {}", self.shipping_company, self.tracking_number) }}
struct Shipment { tracking_info: TrackingInformation,}
impl Shipment { fn status(&self) -> String { format!("Shipped via {}", self.tracking_info.display()) }}
// Afterstruct Shipment { shipping_company: String, tracking_number: String,}
impl Shipment { fn tracking_display(&self) -> String { format!("{}: {}", self.shipping_company, self.tracking_number) }
fn status(&self) -> String { format!("Shipped via {}", self.tracking_display()) }}classDiagram
class ShipmentBefore {
trackingInfo
status()
}
class TrackingInformation {
shippingCompany
trackingNumber
display()
}
class ShipmentAfter {
shippingCompany
trackingNumber
status()
trackingDisplay()
}
ShipmentBefore --> TrackingInformation : has a
TrackingInformation ..> ShipmentAfter : Inline Class Mechanics
Section titled “Mechanics”- On the absorbing class, declare the public methods of the class you are about to inline, and have each simply delegate to the inner instance for now.
- Update every caller of the soon-to-be-removed class to go through the absorbing class instead.
- Run your tests to confirm the delegation behaves identically.
- Move the inner class’s fields and methods into the absorbing class one at a time, using Move Field and Move Function.
- Replace each delegating method body with the real logic now living locally.
- Run your tests after each move so a failure points at a single step.
- Delete the now-empty class once nothing references it.
When to use / trade-offs
Section titled “When to use / trade-offs”Inline a class when it has shrunk to a single field with trivial behaviour, when it exists only to wrap one caller, or when an earlier extraction never grew into a genuine responsibility and now just adds indirection.
The cost is that the absorbing class gets bigger, so do not inline a type that is still doing real, separable work — that is the road back to a Large Class. The inverse is Extract Class: if the merged class later sprouts a second responsibility, split it out again.