Skip to content
FollowtheDamnCode

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

TechniqueMove
Pull Up Field / MethodIdentical members in siblings → move to the parent
Pull Up Constructor BodyShared constructor logic → parent's constructor
Push Down Method / FieldParent member used by one child → move it into that child
Extract SuperclassTwo classes share features → create a common parent
Extract InterfaceSeveral classes share a role → name it
Collapse HierarchyParent and child barely differ → merge them
Form Template MethodSiblings run the same steps with small differences → Template Method
Replace Inheritance with DelegationA subclass uses only part of its parent → hold the former parent as a field
Replace Delegation with InheritanceA 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).

Replace Inheritance with Delegation

In code

Pull up duplicates; replace inheritance with delegation
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: False

Run 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.

Fix it: two siblings drifting apart
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 rows

Run 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

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.