OO Reuse Mechanisms: Inheritance, Composition, Delegation, and Designing for Change
This section from the GoF *Design Patterns* book covers the fundamental mechanisms for achieving reuse in object-oriented systems — class inheritance vs. interface inheritance, composition vs. inheritance, delegation, and parameterized types — and then catalogs common causes of redesign with the patterns that address each. It also distinguishes three levels of software reuse: application programs, toolkits, and frameworks.
Key Concepts
Class inheritance vs. interface inheritance (subtyping): Class inheritance is code/representation sharing; interface inheritance describes substitutability. Many languages (C++, Eiffel) conflate both; Smalltalk inheritance is purely implementation-based.
"Program to an interface, not an implementation": First major design principle. Declare variables in terms of abstract classes/interfaces, not concrete classes. Reduces implementation dependencies between subsystems.
"Favor object composition over class inheritance": Second major design principle. Designers overuse inheritance; composition yields more reusable, simpler designs.
White-box reuse (inheritance): Parent internals visible to subclasses. Static, compile-time. "Inheritance breaks encapsulation" — subclass is coupled to parent's implementation.
Black-box reuse (composition): Objects composed via well-defined interfaces. Dynamic, run-time. Preserves encapsulation; any object replaceable if same type.
Delegation: Makes composition as powerful as inheritance. Receiving object passes this/self to delegate so delegated operations can refer back to the receiver. Example: Window delegates Area to a Rectangle instance rather than subclassing Rectangle.
Parameterized types (generics/templates): Third reuse mechanism. Define types with unspecified type parameters supplied at point of use. Cannot change at run-time (unlike composition).
Aggregation vs. acquaintance: Aggregation implies ownership and identical lifetimes. Acquaintance ("uses"/"association") is weaker, more dynamic, more frequent. Both often implemented identically (pointers/references) — distinguished by intent.
Run-time vs. compile-time structure: Run-time object networks bear little resemblance to static class hierarchies. Design patterns explicitly capture this distinction.
Toolkits: Reusable class libraries emphasizing code reuse; don't impose architecture. Object-oriented equivalent of subroutine libraries.
Frameworks: Sets of cooperating classes defining reusable architecture for a domain. Emphasize design reuse over code reuse. Create inversion of control — framework calls your code, not the reverse.
Commands and Syntax
No commands per se, but key design techniques with examples:
Delegation pattern: Instead of class Window extends Rectangle, Window holds a Rectangle instance variable and forwards Area() to it. Window can swap Rectangle for Circle at run-time.
Three ways to parameterize a sort comparison:
1. Subclass override (Template Method)
2. Pass a strategy object (Strategy)
3. C++ template / Ada generic parameter
Pure interface inheritance in C++: Inherit publicly from pure abstract classes (all pure virtual member functions).
Pure implementation inheritance in C++: Use private inheritance.
Relationships
Creational patterns (Abstract Factory, Builder, Factory Method, Prototype, Singleton) exist specifically to support "program to an interface" by abstracting object creation.
Patterns using delegation: State, Strategy, Visitor (heavily); Mediator, Chain of Responsibility, Bridge (less heavily).
Eight causes of redesign mapped to patterns:
1. Creating by specifying class explicitly → Abstract Factory, Factory Method, Prototype
2. Dependence on specific operations → Chain of Responsibility, Command
3. Platform dependence → Abstract Factory, Bridge
4. Dependence on representations/implementations → Abstract Factory, Bridge, Memento, Proxy
7. Extending by subclassing → Bridge, Chain of Responsibility, Composite, Decorator, Observer, Strategy
8. Inability to alter classes → Adapter, Decorator, Visitor
Patterns capturing run-time structure: Composite, Decorator (building complex structures); Observer, Chain of Responsibility (communication patterns invisible in code).
Exam-Relevant Points
Two core OO design principles: (1) Program to an interface, not an implementation. (2) Favor object composition over class inheritance.
Inheritance breaks encapsulation — subclass is bound to parent's implementation details; changes in parent force subclass changes. Cure: inherit only from abstract classes.
Composition advantages over inheritance: dynamic (run-time) binding, preserves encapsulation, objects replaceable if same type, keeps classes small and focused. Disadvantage: more objects, harder to understand, run-time indirection.
Delegation requires passing self/this to the delegate — this is what distinguishes true delegation from simple forwarding.
Delegation disadvantage: dynamic, parameterized software is harder to understand; works best in "highly stylized ways" (i.e., design patterns).
Framework vs. toolkit: toolkit = you call it (code reuse); framework = it calls you (design reuse + inversion of control).
Frameworks are hardest to design because the architecture must work for all applications in the domain; loose coupling is critical since applications must evolve with the framework.
Aggregation vs. acquaintance: distinguished by intent, not language mechanism. Aggregation = ownership + shared lifetime; acquaintance = weaker, more dynamic.
Parameterized types are a third reuse mechanism alongside inheritance and composition, but cannot change at run-time.
Know which patterns address each of the eight causes of redesign — this is a frequently tested mapping.