Refactoring: Dealing with Generalization
Move behaviour up and down class hierarchies, extract shared parents and interfaces, and swap inheritance for delegation when it stops fitting.
Big idea
Hierarchies drift: siblings grow duplicate methods, parents carry code only one child uses, and some subclasses turn out not to be "kinds of" their parent at all. Generalization refactorings move features to the right level of a hierarchy — or replace the hierarchy with composition when inheritance was the wrong tool.
The techniques
| Technique | Move |
|---|---|
| Pull Up Field / Method | Identical members in siblings → move to the parent |
| Pull Up Constructor Body | Shared constructor logic → parent's constructor |
| Push Down Method / Field | Parent member used by one child → move it into that child |
| Extract Superclass | Two classes share features → create a common parent |
| Extract Interface | Several classes share a role → name it |
| Collapse Hierarchy | Parent and child barely differ → merge them |
| Form Template Method | Siblings run the same steps with small differences → Template Method |
| Replace Inheritance with Delegation | A subclass uses only part of its parent → hold the former parent as a field |
| Replace Delegation with Inheritance | A class forwards everything to one delegate → inherit instead (rarely) |
Think of it like
Sorting a family's shared wardrobe. Things every child wears (school uniform) go in the common cupboard (Pull Up). The tuba only one child plays goes back to their room (Push Down). And the lodger who was treated as "one of the kids" just so he could use the washing machine gets his own key to the laundry instead (Replace Inheritance with Delegation).
In code
class Employee:
def __init__(self, name, monthly):
self.name, self.monthly = name, monthly
def annual_cost(self): # pulled up: was duplicated in both subclasses
return self.monthly * 12 + self.benefits()
def benefits(self):
return 0
class Engineer(Employee):
def benefits(self): return 6000
class Contractor(Employee):
pass
class StackBefore(list): # inherits insert(), sort(), reverse()...
def push(self, x): self.append(x)
class Stack: # delegation: only the stack operations
def __init__(self):
self._items = []
def push(self, x): self._items.append(x)
def pop(self): return self._items.pop()
def peek(self): return self._items[-1]
def __len__(self): return len(self._items)
for e in (Engineer("Ira", 5000), Contractor("Jo", 7000)):
print(f"{e.name}: {e.annual_cost()}")
leaky = StackBefore(); leaky.push(1); leaky.push(2); leaky.insert(0, 99) # allowed, wrongly
safe = Stack(); safe.push(1); safe.push(2)
print("leaky bottom:", leaky[0], "| safe top:", safe.peek(), "| has insert:", hasattr(safe, "insert"))output
Ira: 66000
Jo: 84000
leaky bottom: 99 | safe top: 2 | has insert: FalseRun and edit it in the app.
Watch it
Hands on: form a template method
Two report classes run the same steps — header, rows, footer — and one forgot to escape commas in its rows. Pull the shared sequence up into a base class with a template method, so both use the same escaping. Match the output.
class SalesReport:
def render(self, rows):
out = ["SALES"]
out += [",".join(str(c).replace(",", ";") for c in r) for r in rows]
out.append(f"{len(rows)} rows")
return "\n".join(out)
class StockReport:
def render(self, rows):
out = ["STOCK"]
out += [",".join(str(c) for c in r) for r in rows] # no escaping
out.append(f"{len(rows)} rows")
return "\n".join(out)
print(SalesReport().render([("Pens, blue", 40)]))
print(StockReport().render([("Ink, black", 7)]))output
SALES
Pens; blue,40
1 rows
STOCK
Ink; black,7
1 rowsRun and edit it in the app.
The Vehicle base class has a method load_cargo() that only Truck uses. What's the refactoring?
When should you Replace Inheritance with Delegation?
How it relates
- The mechanics behind Inheritance vs Composition and Liskov Substitution.
- Form Template Method arrives at Template Method; Extract Interface at Interfaces.
- Fixes Refused Bequest, Duplicate Code, and Parallel Hierarchies.
- That completes the refactoring catalogue; next, put it all together in the LLD case studies.
Key terms
- Pull Up Method — Moving identical methods from subclasses into their parent.
- Push Down Method — Moving a parent's method into the only subclass that uses it.
- Extract Superclass — Creating a common parent for classes with shared features.
- Replace Inheritance with Delegation — Turning a parent into a field the class forwards to.