1"""
2Composition vs Inheritance
3
4This demonstrates why "Favor Composition over Inheritance" is a key OOP principle.
5
6Problems with deep inheritance hierarchies:
71. Tight coupling
82. Fragile base class problem
93. Diamond problem (multiple inheritance)
104. Difficult to reason about
115. Hard to extend without breaking existing code
12
13Composition provides:
141. Loose coupling
152. Flexibility
163. Better testability
174. Easier to understand
18"""
19
20from abc import ABC, abstractmethod
21from typing import List, Optional
22
23
24# =============================================================================
25# PROBLEM: Inheritance Hierarchy Gets Out of Control
26# =============================================================================
27
28print("=" * 70)
29print("THE INHERITANCE PROBLEM")
30print("=" * 70)
31
32
33# ❌ BAD: Deep inheritance hierarchy
34class Animal:
35 """Base class"""
36
37 def __init__(self, name: str):
38 self.name = name
39
40 def eat(self):
41 print(f"{self.name} is eating...")
42
43
44class FlyingAnimal(Animal):
45 """Animals that can fly"""
46
47 def fly(self):
48 print(f"{self.name} is flying...")
49
50
51class SwimmingAnimal(Animal):
52 """Animals that can swim"""
53
54 def swim(self):
55 print(f"{self.name} is swimming...")
56
57
58# Problem 1: What about a duck that can both fly AND swim?
59# We'd need multiple inheritance...
60class Duck(FlyingAnimal, SwimmingAnimal):
61 """Duck can fly and swim - uses multiple inheritance"""
62
63 pass
64
65
66# Problem 2: What about a penguin? It swims but doesn't fly!
67class Penguin(SwimmingAnimal):
68 """Penguin swims but doesn't fly"""
69
70 pass
71
72
73# But what if we made Penguin inherit from FlyingAnimal by mistake?
74class PenguinBad(FlyingAnimal):
75 """Inherits fly() but penguins can't fly!"""
76
77 def fly(self):
78 # Have to override and raise error or do nothing
79 raise NotImplementedError("Penguins can't fly!")
80
81
82# Problem 3: What about a robotic dog? It's not an Animal!
83# But we want similar behavior...
84# Can't inherit from Animal (it's not biological)
85# Have to duplicate code or create weird hierarchies
86
87
88# =============================================================================
89# SOLUTION: Composition over Inheritance
90# =============================================================================
91
92print("\n" + "=" * 70)
93print("THE COMPOSITION SOLUTION")
94print("=" * 70)
95
96
97# ✅ GOOD: Define behaviors as separate components
98class FlyingBehavior(ABC):
99 """Abstract flying behavior"""
100
101 @abstractmethod
102 def fly(self, name: str):
103 pass
104
105
106class CanFly(FlyingBehavior):
107 """Concrete flying implementation"""
108
109 def fly(self, name: str):
110 print(f"{name} is flying high in the sky!")
111
112
113class CannotFly(FlyingBehavior):
114 """Concrete non-flying implementation"""
115
116 def fly(self, name: str):
117 print(f"{name} can't fly.")
118
119
120class SwimmingBehavior(ABC):
121 """Abstract swimming behavior"""
122
123 @abstractmethod
124 def swim(self, name: str):
125 pass
126
127
128class CanSwim(SwimmingBehavior):
129 """Concrete swimming implementation"""
130
131 def swim(self, name: str):
132 print(f"{name} is swimming gracefully!")
133
134
135class CannotSwim(SwimmingBehavior):
136 """Concrete non-swimming implementation"""
137
138 def swim(self, name: str):
139 print(f"{name} can't swim.")
140
141
142class EatingBehavior(ABC):
143 """Abstract eating behavior"""
144
145 @abstractmethod
146 def eat(self, name: str):
147 pass
148
149
150class Herbivore(EatingBehavior):
151 def eat(self, name: str):
152 print(f"{name} is eating plants...")
153
154
155class Carnivore(EatingBehavior):
156 def eat(self, name: str):
157 print(f"{name} is eating meat...")
158
159
160class Omnivore(EatingBehavior):
161 def eat(self, name: str):
162 print(f"{name} is eating everything...")
163
164
165# Now compose behaviors into entities
166class ComposedAnimal:
167 """Animal with composable behaviors"""
168
169 def __init__(
170 self,
171 name: str,
172 flying_behavior: FlyingBehavior,
173 swimming_behavior: SwimmingBehavior,
174 eating_behavior: EatingBehavior,
175 ):
176 self.name = name
177 self._flying_behavior = flying_behavior
178 self._swimming_behavior = swimming_behavior
179 self._eating_behavior = eating_behavior
180
181 def fly(self):
182 """Delegate to flying behavior"""
183 self._flying_behavior.fly(self.name)
184
185 def swim(self):
186 """Delegate to swimming behavior"""
187 self._swimming_behavior.swim(self.name)
188
189 def eat(self):
190 """Delegate to eating behavior"""
191 self._eating_behavior.eat(self.name)
192
193 def set_flying_behavior(self, behavior: FlyingBehavior):
194 """Can change behavior at runtime!"""
195 self._flying_behavior = behavior
196
197
198# =============================================================================
199# REAL-WORLD EXAMPLE: Employee Management System
200# =============================================================================
201
202print("\n" + "=" * 70)
203print("REAL-WORLD EXAMPLE: Employee System")
204print("=" * 70)
205
206
207# ❌ BAD: Inheritance-based approach
208class EmployeeBad:
209 def __init__(self, name: str, salary: float):
210 self.name = name
211 self.salary = salary
212
213 def calculate_bonus(self) -> float:
214 return self.salary * 0.1
215
216
217class ManagerBad(EmployeeBad):
218 def calculate_bonus(self) -> float:
219 return self.salary * 0.2
220
221
222class DeveloperBad(EmployeeBad):
223 def code(self):
224 print(f"{self.name} is coding...")
225
226
227class DesignerBad(EmployeeBad):
228 def design(self):
229 print(f"{self.name} is designing...")
230
231
232# Problem: What about a developer who becomes a manager?
233# Or someone who codes AND designs?
234# Inheritance forces rigid categories!
235
236
237# ✅ GOOD: Composition-based approach
238class BonusCalculator(ABC):
239 @abstractmethod
240 def calculate(self, salary: float) -> float:
241 pass
242
243
244class StandardBonus(BonusCalculator):
245 def calculate(self, salary: float) -> float:
246 return salary * 0.1
247
248
249class ManagerBonus(BonusCalculator):
250 def calculate(self, salary: float) -> float:
251 return salary * 0.2
252
253
254class SeniorBonus(BonusCalculator):
255 def calculate(self, salary: float) -> float:
256 return salary * 0.25
257
258
259class Skill(ABC):
260 """Represents a skill an employee can have"""
261
262 @abstractmethod
263 def perform(self, name: str):
264 pass
265
266
267class CodingSkill(Skill):
268 def perform(self, name: str):
269 print(f"{name} is coding...")
270
271
272class DesignSkill(Skill):
273 def perform(self, name: str):
274 print(f"{name} is designing...")
275
276
277class ManagementSkill(Skill):
278 def perform(self, name: str):
279 print(f"{name} is managing the team...")
280
281
282class Employee:
283 """Flexible employee with composable skills and bonus calculation"""
284
285 def __init__(
286 self,
287 name: str,
288 salary: float,
289 bonus_calculator: BonusCalculator,
290 skills: Optional[List[Skill]] = None,
291 ):
292 self.name = name
293 self.salary = salary
294 self._bonus_calculator = bonus_calculator
295 self._skills = skills or []
296
297 def calculate_bonus(self) -> float:
298 return self._bonus_calculator.calculate(self.salary)
299
300 def add_skill(self, skill: Skill):
301 """Can add skills dynamically!"""
302 self._skills.append(skill)
303
304 def perform_skills(self):
305 """Execute all skills"""
306 for skill in self._skills:
307 skill.perform(self.name)
308
309 def promote(self, new_bonus_calculator: BonusCalculator):
310 """Change bonus calculation (e.g., promotion)"""
311 self._bonus_calculator = new_bonus_calculator
312
313
314# =============================================================================
315# DEMONSTRATION
316# =============================================================================
317
318def demonstrate_inheritance_problems():
319 print("\n[INHERITANCE APPROACH]")
320 print("-" * 50)
321
322 duck = Duck("Donald")
323 duck.eat()
324 duck.fly()
325 duck.swim()
326
327 penguin = Penguin("Pingu")
328 penguin.eat()
329 penguin.swim()
330 # penguin.fly() # Doesn't have fly method
331
332 penguin_bad = PenguinBad("BadPingu")
333 try:
334 penguin_bad.fly() # Will raise error
335 except NotImplementedError as e:
336 print(f"Error: {e}")
337
338 print("\nProblems with inheritance:")
339 print(" ✗ Rigid hierarchies")
340 print(" ✗ Hard to mix behaviors (flying + swimming)")
341 print(" ✗ Breaking Liskov Substitution Principle")
342 print(" ✗ Can't change behavior at runtime")
343
344
345def demonstrate_composition_solution():
346 print("\n[COMPOSITION APPROACH]")
347 print("-" * 50)
348
349 # Duck: can fly, can swim, eats everything
350 duck = ComposedAnimal(
351 "Donald",
352 flying_behavior=CanFly(),
353 swimming_behavior=CanSwim(),
354 eating_behavior=Omnivore(),
355 )
356 duck.fly()
357 duck.swim()
358 duck.eat()
359
360 # Penguin: cannot fly, can swim, eats fish
361 penguin = ComposedAnimal(
362 "Pingu",
363 flying_behavior=CannotFly(),
364 swimming_behavior=CanSwim(),
365 eating_behavior=Carnivore(),
366 )
367 penguin.fly()
368 penguin.swim()
369 penguin.eat()
370
371 # Robot bird: can fly (with motors), cannot swim, doesn't eat
372 robot = ComposedAnimal(
373 "RoboBird",
374 flying_behavior=CanFly(),
375 swimming_behavior=CannotSwim(),
376 eating_behavior=Herbivore(), # Charges battery instead
377 )
378 robot.fly()
379
380 # Change behavior at runtime!
381 print("\n[RUNTIME BEHAVIOR CHANGE]")
382 print("Penguin learns to fly (with jetpack):")
383 penguin.set_flying_behavior(CanFly())
384 penguin.fly()
385
386 print("\nBenefits of composition:")
387 print(" ✓ Flexible behavior mixing")
388 print(" ✓ Runtime behavior changes")
389 print(" ✓ Easy to test (mock behaviors)")
390 print(" ✓ Follows Open/Closed Principle")
391 print(" ✓ No inheritance coupling")
392
393
394def demonstrate_employee_system():
395 print("\n[EMPLOYEE SYSTEM EXAMPLE]")
396 print("-" * 50)
397
398 # Junior developer
399 alice = Employee(
400 "Alice",
401 salary=60000,
402 bonus_calculator=StandardBonus(),
403 skills=[CodingSkill()],
404 )
405 alice.perform_skills()
406 print(f"Bonus: ${alice.calculate_bonus()}")
407
408 # Senior developer who also designs
409 bob = Employee(
410 "Bob",
411 salary=90000,
412 bonus_calculator=SeniorBonus(),
413 skills=[CodingSkill(), DesignSkill()],
414 )
415 print(f"\n{bob.name}'s skills:")
416 bob.perform_skills()
417 print(f"Bonus: ${bob.calculate_bonus()}")
418
419 # Promote Alice to manager
420 print(f"\n{alice.name} gets promoted to manager!")
421 alice.promote(ManagerBonus())
422 alice.add_skill(ManagementSkill())
423 alice.perform_skills()
424 print(f"New bonus: ${alice.calculate_bonus()}")
425
426 print("\nComposition allows:")
427 print(" ✓ Employees with multiple skills")
428 print(" ✓ Dynamic skill addition")
429 print(" ✓ Easy promotions (change bonus calculator)")
430 print(" ✓ No rigid job categories")
431
432
433def print_comparison():
434 print("\n" + "=" * 70)
435 print("COMPOSITION VS INHERITANCE")
436 print("=" * 70)
437
438 print("""
439WHEN TO USE INHERITANCE:
440 ✓ True "is-a" relationship (Cat IS-A Animal)
441 ✓ Shared implementation across subtypes
442 ✓ Shallow hierarchies (1-2 levels)
443 ✓ Liskov Substitution Principle holds
444
445WHEN TO USE COMPOSITION:
446 ✓ "has-a" or "can-do" relationships
447 ✓ Need to mix multiple behaviors
448 ✓ Want to change behavior at runtime
449 ✓ Need flexibility and loose coupling
450 ✓ Want to avoid fragile base class problem
451
452KEY PRINCIPLE:
453 "Favor composition over inheritance"
454
455 This doesn't mean NEVER use inheritance.
456 It means: think carefully before inheriting,
457 and consider if composition is better.
458
459COMPOSITION ADVANTAGES:
460 • More flexible
461 • Better testability
462 • Easier to reason about
463 • No deep hierarchies
464 • Runtime behavior changes
465 • Follows SOLID principles better
466
467INHERITANCE ADVANTAGES:
468 • More intuitive for true "is-a" relationships
469 • Less boilerplate for simple cases
470 • Built-in language support
471""")
472
473
474if __name__ == "__main__":
475 demonstrate_inheritance_problems()
476 demonstrate_composition_solution()
477 demonstrate_employee_system()
478 print_comparison()