I'm wondering about the differences between inheritance and composition examined with concrete code relevant arguments.
In particular my example was:
Inheritance:
class Do:
def do(self):
self.doA()
self.doB()
def doA(self):
pass
def doB(self):
pass
class MyDo(Do):
def doA(self):
print("A")
def doB(self):
print("B")
x=MyDo()
vs Composition:
class Do:
def __init__(self, a, b):
self.a=a
self.b=b
def do(self):
self.a.do()
self.b.do()
x=Do(DoA(), DoB())
(Note for composition I'm missing code so it's not actually shorter)
Can you name particular advantages of one or the other?
I'm think of:
- composition is useful if you plan to reuse DoA() in another context
- inheritance seems easier; no additional references/variables/initialization
- method doA can access internal variable (be it a good or bad thing 🙂 )
- inheritance groups logic A and B together; even though you could equally introduce a grouped delegate object
- inheritance provides a preset class for the users; with composition you'd have to encapsule the initialization in a factory so that the user does have to assemble the logic and the skeleton
- …
Basically I'd like to examine the implications of inheritance vs composition. I heard often composition is preferred, but I'd like to understand that by example.
Of course I can always start with one and refactor later to the other.
Best Answer
Conceptually speaking, composition models "consists of" relationships, whereas inheritance models "is a".
Using the car analogy, "a car has wheels" is a textbook example for composition, and it makes sense to have a class
Wheel
, and a classCar
with a propertywheels
of typeWheel[]
.In theory, an example of inheritance would be "a truck is a vehicle": properties common to all vehicles can be implemented in class
Vehicle
, while those specific to trucks can be implemented in classTruck
.The truck example, however, also illustrates the problem of the inheritance approach: what if you have to make your vehicle class polymorphic not only for the vehicles purpose (passengers vs. freight), but also for fuel type? You'd have to create four classes to cover passenger cars and freight vehicles, as well as diesel vs. gasoline powered. Add another orthogonal property, and the number of classes doubles again. Worse yet, you have to decide which of these orthogonal properties comes first in the class hierarchy: is it
Vehicle
->DieselVehicle
->DieselFreightVehicle
->Truck
, or is itVehicle
->FreightVehicle
->DieselFreightVehicle
->Truck
? Either way, you have to duplicate some functionality, either the freight-specific things, or the diesel-specific things. The solution is to use composition anyway: A vehicle has an engine (which can be diesel- or gasonline-powered), and a cargo type (which can be passengers or freight); you don't need a truck class anymore, because your vehicle class can already model all sorts of vehicles by combining suitable engines and cargo types. Note that the components are still polymorphic, but the container object is not. The trick is to keep polymorphism one-dimensional, that is, each polymorphic hierarchy models one of many orthogonal properties, such as engine type, freight type, etc.This is why people say "favor composition over inheritance"; of course you are still inheriting, but you have separated your inheritance into independent strains.