Skip to content

Extract Superclass & Interface

Extract Superclass spots two classes that have independently grown to share fields and behaviour, and introduces a new common parent to hold the overlap — then pulls the shared members up into it. Extract Interface is the lighter cousin: when classes share only a protocol (a set of method signatures clients depend on) but no implementation, you extract that protocol into an interface — a trait in Rust, a structural interface in Go — so callers can depend on the contract rather than the concrete type.

Two classes with a suspicious overlap of fields and methods are a quiet form of Duplicated Code — neither knows the other exists, yet both maintain a name, a monthlyCharge, an address. Extract Superclass gives that overlap a home. When the overlap is purely behavioural — several unrelated classes that all need to be billed, or rendered, or compared — and they share no data, an interface captures the contract without forcing a false data hierarchy.

A Department and an Employee both carry a name and compute a monthly charge. We extract a Party superclass for the shared parts, and a Billable interface for the shared protocol.

// Before — two classes independently grew the same shape
class Department {
constructor(public name: string, public staffCount: number) {}
monthlyCharge(): number {
return this.staffCount * 100;
}
}
class Employee {
constructor(public name: string, public salary: number) {}
monthlyCharge(): number {
return this.salary / 12;
}
}
// After — shared protocol as an interface, shared field via a superclass
interface Billable {
monthlyCharge(): number;
}
abstract class Party implements Billable {
constructor(public name: string) {}
abstract monthlyCharge(): number;
}
class Department extends Party {
constructor(name: string, public staffCount: number) {
super(name);
}
monthlyCharge(): number {
return this.staffCount * 100;
}
}
class Employee extends Party {
constructor(name: string, public salary: number) {
super(name);
}
monthlyCharge(): number {
return this.salary / 12;
}
}
classDiagram
  class Party {
    +name string
    +monthlyCharge() number
  }
  class Department
  class Employee
  Party <|-- Department
  Party <|-- Employee
  class Billable {
    <<interface>>
    +monthlyCharge() number
  }
  Billable <|.. Party
Department and Employee gain a shared Party parent and a Billable contract
  1. Confirm the overlap. List the fields and methods the two classes share. If they share state and behaviour, lean toward a superclass; if they share only signatures, lean toward an interface or trait.
  2. Create the empty superclass (or interface / trait). In Go and Rust, create the shared struct and the interface or trait.
  3. For a superclass: make the two classes extend it. Then Pull Up the shared fields and methods one at a time, running your tests after each.
  4. For an interface: declare the shared method signatures, then make each class declare that it implements the interface (automatic in Go’s structural typing; explicit impl in Rust).
  5. Point client code at the new abstraction — accept the interface or superclass type wherever it does not need the concrete one.
  6. Run your tests after each move.

Extract Superclass when two classes share real implementation you are tired of maintaining twice. Extract Interface when many types must be treated uniformly by clients but share no data — for instance, anything “billable”, “serializable”, or “comparable”.

The trade-off: a superclass couples its children together and is a one-shot in single-inheritance languages, so spend it carefully. An interface costs nothing structurally but adds a layer of indirection. In Go and Rust, Extract Interface (interface/trait) is the default tool and Extract Superclass is replaced by composing a shared struct — there is no class parent to extract.

When should you prefer Extract Interface over Extract Superclass?
In Go, how is "extract interface" expressed?
In Rust, what plays the role of an extracted interface?
What is a key cost of extracting a superclass in a single-inheritance language?