Refactoring techniqueDealing with Generalization
Replace Inheritance with Delegation
Turn a subclass that uses only part of its superclass into a class that holds an instance of it and forwards just the calls it needs.
- Also known as
- Replace Superclass with Delegate, Replace Subclass with Delegate
Problem
A class extends another to reuse some of its code, but it is not truly a kind of that class. It inherits methods that make no sense for it, and callers can use them to break its rules.
Solution
Give the class a field holding an instance of the former superclass, forward the calls you actually need to it, and remove the extends.
Before
class AuditLog extends Array<AuditEntry> {
record(actorId: string, action: string, target: string): void {
this.push({ actorId, action, target, at: new Date() });
}
byActor(actorId: string): AuditEntry[] {
return this.filter((e) => e.actorId === actorId);
}
}
const log = new AuditLog();
log.record("u-7", "role.grant", "u-12");
log.splice(0, 1); // compiles: entries can be erased from an append-only logAfter
class AuditLog {
private readonly entries: AuditEntry[] = [];
record(actorId: string, action: string, target: string): void {
this.entries.push({ actorId, action, target, at: new Date() });
}
byActor(actorId: string): AuditEntry[] {
return this.entries.filter((e) => e.actorId === actorId);
}
get size(): number {
return this.entries.length;
}
}
const log = new AuditLog();
log.record("u-7", "role.grant", "u-12");
// log.splice(...) no longer existsWhy
- The class exposes only operations that keep its invariants: append-only stays append-only.
- Changes to the former superclass no longer leak into the public API.
- You can swap the delegate for another implementation, such as a database-backed store.
Steps
- Add a field of the superclass type to the subclass, initialized to
this. Run the tests. - Change each method in the subclass that uses inherited members to call them through the field. Run the tests.
- List the inherited methods callers actually use. Add a forwarding method for each one that should stay public.
- Remove the
extendsclause and initialize the field with a new instance of the former superclass. Run the compiler and tests. - Fix callers that used the class as the superclass type (for example, passed it where an array was expected). Give them an explicit accessor or an interface.
Watch out
- Do not use delegation when the subclass truly is a specialization and uses most of the superclass interface. You will write many trivial forwarding methods.
- Forwarding breaks
instanceofchecks and code that passes the object where the superclass type is expected. - Calls that the superclass made to overridden methods (self-calls) no longer reach the former subclass. Check any template-method style hooks.
- The inverse technique is Replace Delegation with Inheritance.
Related
- Inverse: Replace Delegation with Inheritance.
- Fixes Refused Bequest and reduces Inappropriate Intimacy with the superclass's internals.
- Often combined with Extract Interface. The resulting structure underlies Decorator and Strategy.
Agent checklist
- The class uses only a small part of the superclass interface, or inherited methods break its rules.
- Only methods that callers need are forwarded.
- Callers relying on the superclass type or
instanceofwere updated. - Former self-calls from the superclass into overrides were checked.
Treats these smells
- Refused BequestA subclass inherits methods or data it does not want, overriding them to throw or do nothing, so the hierarchy lies about what it can do.
- Inappropriate IntimacyTwo classes reach into each other's private fields and internals. Cut the back-and-forth by moving members or narrowing what each exposes.
See also
- Replace Delegation with InheritanceWhen a class forwards nearly every call to one delegate it truly is a kind of, make it a subclass of that delegate and delete the forwarding.
- Extract InterfaceDeclare the subset of a class's methods that clients actually use as an interface, so clients depend on the contract instead of the class.
- Hide DelegateAdd a method on the server object that forwards to its delegate, so clients stop navigating through it to reach the delegate.
- DecoratorWrap an object in another object with the same interface that adds behavior before or after delegating, so features stack without subclassing.
- StrategyPut each interchangeable algorithm behind one interface so the caller picks a behavior at runtime without branching on it.
Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.