Step 1 — name the responsibilities. A car must start(). Starting needs ignition logic. Why split? Because ignition is a self-contained concept that may change independently (petrol vs electric).
Step 2 — give the sub-responsibility its own class.
class Engine: def __init__(self, horsepower): self.horsepower = horsepower def ignite(self): return f"Engine roaring at {self.horsepower}hp"
Why this step? We isolate the volatile part so the Car never touches its internals.
Step 3 — the Car HOLDS an Engine (composition) and delegates.
class Car: def __init__(self, engine): # engine passed IN self.engine = engine # the has-a field def start(self): return "Car: " + self.engine.ignite() # delegation
Why pass it in (dependency injection)? So we can give any engine — petrol, electric, mock — without editing Car.
Step 4 — use it.
c = Car(Engine(120))print(c.start()) # Car: Engine roaring at 120hpc.engine = Engine(300) # swap at runtime — impossible with inheritance!
Imagine LEGO. Inheritance is like saying "a race-car is a special kind of car" — it's the same toy, just fancier. Composition is like building a car out of separate LEGO pieces: wheels, an engine block, a seat. If you don't like the engine, you pop it off and click a new one in — you don't rebuild the whole car. That popping-and-swapping is why builders love composition: the car just has an engine, it doesn't become one.
"has-a" — a class holds another class as a field and delegates to it.
What relationship does inheritance model?
"is-a" — the subclass is a kind of the superclass and must be substitutable for it.
The English test for is-a vs has-a?
Read "A is a B" vs "A has a B" aloud; pick whichever is grammatically true.
Why is "favor composition over inheritance" advised?
Composition couples loosely via a small public interface; inheritance tightly couples to the parent's internals and breaks on parent changes (fragile base class).
Why is class Stack(list) a bad is-a design?
It inherits list's full interface (insert, indexing, slicing), letting users violate LIFO. Use has-a + a hidden list instead.
How does composition avoid combinatorial class explosion?
Behaviors become swappable part-objects mixed at runtime, so you don't need a separate class per combination.
What is delegation?
A method forwards work to a held object, e.g. Car.start() calls self.engine.ignite().
What deep type rule must inheritance satisfy?
Liskov substitution — a subclass must work anywhere the superclass is expected.
Runtime advantage unique to composition?
You can swap the held part while the program runs (car.engine = ElectricEngine()); inheritance is fixed at compile time.
Dekho, jab tum koi naya class banate ho, do hi sawaal poochne hote hain: kya yeh cheez kisi cheez ko rakhti hai (has-a), ya kya yeh kisi cheez ka type hai (is-a)? Agar "Car has-a Engine" natural lagta hai, to Engine ko ek field ki tarah store karo aur uske methods ko call karo — isko composition kehte hain. Agar "Dog is-a Animal" natural lagta hai, to inheritance use karo. Bas English mein sentence bol ke check kar lo — grammar tumhe design bata degi.
Ab sabse important baat: log kehte hain "favor composition over inheritance". Kyun? Kyunki inheritance mein child class apne parent ke saare internals inherit kar leti hai, aur agar parent badla to child silently toot sakta hai (fragile base class problem). Composition mein tum sirf part ke chote se public interface pe depend karte ho — yeh loosely coupled hai, isliye safe hai. Aur ek bonus: tum runtime pe part swap kar sakte ho, jaise car.engine = ElectricEngine() — inheritance mein yeh impossible hai.
Classic galti: class Stack(list) likhna kyunki "stack ek list hi to hai". Galat! Ab user stack.insert(0, x) ya stack[5] kar ke beech mein ghus sakta hai aur LIFO break ho jaata hai. Iska fix — Stack ke andar ek private list rakho (has-a), aur sirf push/pop/size public rakho. Yaad rakho mnemonic: "HAS a part, IS a kind" — agar confuse ho to has-a hamesha safe choice hai.