Prefer Composition Over Inheritance
Why You Inherit in the First Place
You generally inherit from a class when you want to reuse some data/functionality from that class.
For example, if you have a Bird
class with a fly
method:
class Bird:
def fly(self):
print("flap flap flap")
You could reuse the fly
method by creating a Sparrow
class that inherits from Bird
:
class Sparrow(Bird):
pass
# fly method is inherited
# other sparrow-specific methods
Now, let’s say you have a Fish
class with a swim
method:
class Fish:
def swim(self):
print("swish swish swish")
Similarly, you could create a Shark
class that inherits from Fish
:
class Shark(Fish):
pass
# swim method is inherited
# other shark-specific methods
What’s the Problem With Inheritance?
But what if you want to create a Duck
class that can both fly and swim? You could create a Duck
class that inherits from both Bird
and Fish
, but as we know there are massive problems with multiple inheritance.
What’s the Alternative? (hint: Composition)
So, let’s reel it back a bit and think about what we’ve done. We had a fly
method (behavior) and a swim
method (behavior), and all we wanna do is be able to reuse these methods (behaviors) in other classes, namely Sparrow
, Shark
, and Duck
.
So, instead of inheriting these behaviors, we will delegate via composition, to little objects that implement these behaviors.
An example will make it all clear. These are the behaviors (methods) we want to reuse:
class FlyBehavior:
def fly(self):
print("flap flap flap")
class SwimBehavior:
def swim(self):
print("swish swish swish")
And here is how we can easily use them in a flexible mix and match way, in a variety of classes:
class Sparrow:
def __init__(self):
self.fly_behavior = FlyBehavior()
def fly(self):
self.fly_behavior.fly()
class Shark:
def __init__(self):
self.swim_behavior = SwimBehavior()
def swim(self):
self.swim_behavior.swim()
class Duck:
def __init__(self):
self.fly_behavior = FlyBehavior()
self.swim_behavior = SwimBehavior()
def fly(self):
self.fly_behavior.fly()
def swim(self):
self.swim_behavior.swim()
The other advantage here is that we can easily change the behavior of a class at runtime. If we have a JetFlyBehavior
we can just assign that to our duck like so:
class JetFlyBehavior:
def fly(self):
print("whoosh whoosh whoosh")
duck = Duck()
duck.fly_behavior = JetFlyBehavior()
duck.fly() # duck on steroids lol
Summary
Let’s summarize the key points!
- Don’t inherit data/functionality, delegate (compose) to it. This means put the data/functionality in little classes and then delegate to instances of those classes.
- This allows a single class to have multiple behaviors, without the headaches of multiple inheritance.
- Additionally, you can change behaviors during runtime!
- Interfaces are fine, but in duck typed languages like python, you don’t even need them! But if you need em, create em. Just make sure they are small and focused (think “interface segregation principle”).