Inheritance and composition are one of the most concepts in object-oriented programming. Where the former one attempts to share a behaviour across different classes, the latter is mostly think of as a way to combine simple objects (or data types) in order to build a more complex ones. At the first glance they may seem to be designed for different purposes. But are they?
I bet most, if not all, programmers know what inheritance is. For the sake of completeness a quick example in Python:
class Parent: def some_method(self): pass class Child(Parent): def other_method(self): pass
Parent class, so instance of it can
other_method. We passed behaviour without a need
to copy and paste any code, which is nice.
This, of course, can lead to more advanced design patterns like template method, which uses inheritance in order to define a skeleton of an algorithm and expect from child classes to add some flesh to it :-) An example:
class Skeleton: def do_the_stuff(self, arg1, other_arg): # maybe do some stuff before self._some_part_of_job() # can do more stuf after as well def _some_part_of_job(self): raise NotImplementedError class Flesh(Skeleten): def _some_part_of_job(self): pass
Now, as you can see,
Flesh is using invariant steps, defined in its base
Skeleton. It only implements different behaviour in
_some_part_of_job method, but overall algorithm is still the same.
might provide a default implementation but it’s not a necessity. Of course one
class may define more template methods, if needed.
As you can see I fulfill my goal to not repeat myself. But did I keep it simple? My code is DRY, but will I get a KISS for this?
(Explanation for this bad pun: one of software development principles is DRY which stands for Don’t Repeat Yourself, another one is KISS which reads Keep It Simple, Stupid)
As described before, composition is used to create more complex objects or data types from simpler ones. Some basic example:
class Animal: def __init__(self): self.name = '' self.age = 0
As you can see in the example,
Animal class consist of two fields:
age. So composition in OOP is something you do all the time. Booooring. Let’s
move to something more interesting, shall we?
We can compose objects to share behaviour as well. It would be called delegation in such scenario and here’s how you implement this:
class DBProvider: def __init__(self): # connect to a database or whatever def find(self, uid): return class User: provider = DBProvider() def find(self, uid): data = self.provider.find('user', uid) return User(**data)
This looks pretty similar to the inheritance. So why I’ve chosen this approach to implement database operations in Łapka? Well, separation of concerns was my concern.
See, there may be much more data providers then a database one - maybe a cache, file or even a remote service. They have nothing in common, when it comes to a implementation. They have common interface, of course, but here’s where similarity ends. Underlying code will be completely different, so inheritance doesn’t look natural for me here.
And there is another problem. What if my application will need to use multiple persistence implementation at once? Should I create multiple classes of User, each inheriting from different base class? This sounds just lame. It’s better to use composition and delegate persistence to instance of another class. Plus, in unit tests I can substitute a real provider with a dummy one to make my tests run faster.
Just use what seems to be a better fit for a problem you’re trying to solve. Inheritance is not always the answer.