6 Composition over Inheritance
I’ll demonstrate why composition is superior to inheritance by implementing both approaches from the diagrams, showing how the Strategy Pattern solves the combinatorial explosion problem.This example perfectly demonstrates why “Favor Composition over Inheritance” is a fundamental design principle. Here’s why:
6.1 Problems with Inheritance Approach:

NoteExample Dart
// ============================================
// PROBLEM: Inheritance Approach (Combinatorial Explosion)
// ============================================
// Base class
abstract class TransportInheritance {
void deliver(String destination, String cargo);
}
// First level: Cargo type
abstract class Truck extends TransportInheritance {}
abstract class Car extends TransportInheritance {}
// Second level: Engine type (2 cargo × 2 engine = 4 classes)
abstract class ElectricTruck extends Truck {}
abstract class CombustionEngineTruck extends Truck {}
abstract class ElectricCar extends Car {}
abstract class CombustionEngineCar extends Car {}
// Third level: Navigation type (4 × 2 navigation = 8 classes!)
class AutopilotElectricTruck extends ElectricTruck {
@override
void deliver(String destination, String cargo) {
'Autopilot Electric Truck delivering $cargo to $destination');
print(}
}
class AutopilotCombustionEngineTruck extends CombustionEngineTruck {
@override
void deliver(String destination, String cargo) {
'Autopilot Combustion Truck delivering $cargo to $destination');
print(}
}
class AutopilotElectricCar extends ElectricCar {
@override
void deliver(String destination, String cargo) {
'Autopilot Electric Car delivering $cargo to $destination');
print(}
}
class AutopilotCombustionEngineCar extends CombustionEngineCar {
@override
void deliver(String destination, String cargo) {
'Autopilot Combustion Car delivering $cargo to $destination');
print(}
}
// ... And 4 more classes for manual driving!
// Total: 8 classes for just 3 dimensions!
- Combinatorial Explosion:
- 2 cargo types × 2 engines × 2 drivers = 8 classes
- Adding just one more dimension (like transmission type) doubles it to 16 classes!
- Rigid Structure:
- Can’t change behavior at runtime
- Each combination needs a new class
- Deep inheritance hierarchies become fragile
- Violates DRY:
- Similar code repeated across multiple classes
- Changes need to be made in multiple places
6.2 Benefits of Composition (Strategy Pattern):

NoteExample Dart
// ============================================
// SOLUTION: Composition Approach (Strategy Pattern)
// ============================================
// Strategy Interfaces
abstract class Engine {
void move();
}
abstract class Driver {
void navigate(String destination);
}
// Concrete Engine Strategies
class CombustionEngine implements Engine {
@override
void move() {
'Moving with combustion engine: vroom vroom!');
print(}
}
class ElectricEngine implements Engine {
@override
void move() {
'Moving with electric engine: silent whoosh~');
print(}
}
// Concrete Driver Strategies
class Human implements Driver {
@override
void navigate(String destination) {
'Human driver navigating to $destination using GPS');
print(}
}
class Robot implements Driver {
@override
void navigate(String destination) {
'Autopilot navigating to $destination using AI');
print(}
}
// Transport class using composition
class Transport {
final String type; // "Truck" or "Car"
final Engine engine;
final Driver driver;
{
Transport(this.type,
required this.engine,
required this.driver,
required });
void deliver(String destination, String cargo) {
'\n--- $type Delivery ---');
print('Cargo: $cargo');
print(.navigate(destination);
driver.move();
engine'Delivery complete!');
print(}
}
- Linear Growth:
- Only 2 + 2 + 2 = 6 classes instead of 8
- Adding new dimension adds classes, not multiplies them
- Runtime Flexibility:
- Can create any combination without new classes
- Can change behaviors dynamically
- Better Design Principles:
- Single Responsibility: Each class has one job
- Open/Closed: Add features without modifying existing code
- Dependency Inversion: Depend on abstractions (interfaces)
- Easier Testing:
- Can mock individual strategies
- Test each component in isolation
- Real-world Modeling:
- More accurately represents reality (a truck HAS an engine, not IS an engine)
The Strategy Pattern shown here is just one example of composition. This principle applies broadly in software design, leading to more maintainable, flexible, and understandable code.
6.3 Usage Benefits
INHERITANCE HIERARCHY (Explodes quickly):
Transport
/ \
Truck Car
/ \ / \
Electric Combustion Electric Combustion
| | | |
Autopilot Autopilot Autopilot Autopilot
Manual Manual Manual Manual
8 leaf classes for 3 dimensions!
COMPOSITION STRUCTURE (Scales linearly):
Transport
├── engine: Engine ←── CombustionEngine
│ ←── ElectricEngine
│ ←── HydrogenEngine (easy to add!)
│
└── driver: Driver ←── Human
←── Robot
←── RemoteDriver (easy to add!)
Only 6 classes total, infinite combinations!
Example Code:
void main() {
'=== INHERITANCE APPROACH PROBLEMS ===');
print('Problem 1: Combinatorial Explosion');
print('- 2 cargo types × 2 engines × 2 drivers = 8 classes needed!');
print('- Adding 1 new dimension (e.g., transmission) doubles classes to 16!');
print(
'\nProblem 2: Rigid Structure');
print(var inheritanceTruck = AutopilotElectricTruck();
.deliver('Warehouse', 'Packages');
inheritanceTruck'Cannot change engine or driver at runtime!');
print(
'\n\n=== COMPOSITION APPROACH BENEFITS ===');
print(
// Create different combinations easily
var electricAutopilotTruck = Transport(
: 'Truck',
type: ElectricEngine(),
engine: Robot(),
driver
);
var combustionHumanCar = Transport(
: 'Car',
type: CombustionEngine(),
engine: Human(),
driver
);
.deliver('Distribution Center', 'Electronics');
electricAutopilotTruck.deliver('Customer House', 'Groceries');
combustionHumanCar
'\n=== RUNTIME FLEXIBILITY ===');
print(// Can create any combination without new classes
var hybridVehicle = Transport(
: 'Truck',
type: ElectricEngine(),
engine: Human(),
driver
);.deliver('Store', 'Mixed cargo');
hybridVehicle
// Can even swap components (with a factory method)
'\n=== EASY EXTENSION ===');
print(
// Adding new engine type - just one new class!
class HydrogenEngine implements Engine {
@override
void move() {
'Moving with hydrogen fuel cell: eco-friendly!');
print(}
}
// Adding new driver type - just one new class!
class RemoteDriver implements Driver {
@override
void navigate(String destination) {
'Remote operator navigating to $destination via 5G');
print(}
}
// Use immediately without touching existing code
var futuristicTruck = Transport(
: 'Truck',
type: HydrogenEngine(),
engine: RemoteDriver(),
driver
);.deliver('Space Port', 'Satellites'); futuristicTruck