Favour composition over inheritance: A real world OO example
So in my current work I'm real busy with Object Oriented programming.
With that come OO principles like favour composition over inheritance. Does that make composition a rule of thumb?
Let's look at an example. Not just one from the top of my head, but one that we actually use in the real world…
Well, that is not really a real-world example, but we will slowly get there. Lets first explain what we see here:
So what do we see here? Yes, you got it, a class-diagram. But what are we trying to keep track of with these classes? Well, products that people might choose in a shoppingBag on that great website we have where we want to sell.. well, flightbookings and hotelbookings! In this case, our shoppingBag can contain either a flightproduct or a hotelproduct. The thing is that this class diagram has a lot of drawbacks, mainly because we have got a set of behaviours that are valid for both of the flight- and hotelproducts, like they can actually be added to or removed from the shoppingbag and these products can both be booked, or cancelled, both have a price, discount and so on. So for every product that we want to sell on the site we are programming alot of similar stuff. And think about what happens if new products are to be sold on the site. Awfull. To prevent coding alot of similar stuff, we of course invented a class to abstract some of those common behaviours..
Behold the abstract class product:
Now all the code in the shoppingbag class suddenly does not care if it is talking to or about flights, hotels, carhire or travel-insurances or whatever stuff we want to sell on our website, as long as all products share the same behaviour that we now have put in the general abstract product class. This all works very vell.
The thing now is that if our application grows and more (product)feautures are to be added, it is very tempting to add those feautures to that product class. But that should not be done, mainly because of the following reason:
Not all products that inherit from the product class, will use that new feauture. Sometimes, only some of the actual products will use new feautures. Therefore we should not extend the abstract class with all kinds of new behaviour.
The OO principle behind this is that one class should have only one reason to change.
Lets see what happend to the class diagram when new behaviour was necessary:
Oh yes, this really is a real world example, right from a real WEBApplication where we sell, well, flights and hotels (and more of course).
The thing is, we also like our clients to sell other goodies when they have already selected a product. This is called "cross selling" and it is actually based on some general behaviours that all of our products have, like location and the time of travel. Concrete: if somebody is flying to Barcelona, we would like to present the user of our site a Hotel in Barcelona rather than, say, Reykjavik..
So, put the Location and PeriodOfTravel in the product class?
Not really. Some products (like, insurance or goodies) really don't have a location or travelperiod like the travelproducts do have so we did not add those items to the already existing abstract product class.
We do not want to use that one class for different purposes, that would make our code messy.
So that is why we created a new class for that specific behaviour and put those properties in the Interface ICrossSellable.
But than why did we not compose the product class to hold that interface? That would have been possible:
Using composition it is perfectly valid that the productclass has a property that holds the behaviour, or not, if that is not needed for the actual product.
Still, we used inheritance instead of composition. Why because as the GOF states it: because sometimes inheritance is simpler to understand than composition.
Now before clicking on that link, it is actually citing a piece of the GOF book:
One advantage of composition is that it is more flexible because behavior can be swapped at runtime. One disadvantage of composition is that the behavior of the system may be harder to understand just by looking at the source. These are all factors one should think about when applying composition over inheritance.
In real world applications, it is not about scientifically writing the best possible code. It is about writing the best maintainable code for that moment. That sometimes means we do not follow the OO principles from the start. Is that a problem? Of course not. We can still refactor later to let our code be even more maintainable and our software stand the demands of future changes, and if OO principles will help us than we are glad we at least know about them 🙂