Choosing composition over inheritance: yet another example!
Julien on May 30th 2008
A friend of mine recently asked me if I though that having an inheritance hierarchy with a depth of 10 classes was acceptable or not. I don't think it is. If you take a class at the bottom, any change to the 9 classes above can introduce a breaking change. It can quickly become a maintenance nightmare!
Most of the time, if you have that kind of hierarchy in your application, it means that you chose inheritance over composition. It's probably also a code-smell indicating that your class is doing too much. To avoid that, let me show you how it is possible to extend a class without using inheritance.
Let's assume that we start with the following code. I know it's very basic, however it's still enough for our demonstration.
abstract class TripBase { private readonly string _from; private readonly string _to; public string From { get { return _from; } } public string To { get { return _to; } } protected TripBase(string from, string to) { _from = from; _to = to; } public abstract DateTime CalculateEstimatedArrivalTime(); } class CarTrip : TripBase { public CarTrip(string from, string to) : base(from, to) { } public override DateTime CalculateEstimatedArrivalTime() { return DateTime.Now.AddHours(15); } } class PlaneTrip : TripBase { public PlaneTrip(string from, string to) : base(from, to) { } public override DateTime CalculateEstimatedArrivalTime() { return DateTime.Now.AddHours(2); } }
We have a BaseTrip abstract class and we want to extend it by having 2 sub classes: CarTrip and PlaneTrip. In a real application, we would have some kind of algorithm to calculate or fetch the transportation time and it would probably be very different for each class. In this example, to simplify, we just return a hard coded value.
This code is not bad, however it's not perfect either. For instance, are we sure that calculating the transportation time between 2 city is the responsibility of the CarTrip and the PlaneTrip class? As a matter of fact, if we want to add a new MotocycleTrip class that would have different properties but the same algorithm than CarTrip to calculate the transportation time, we would need to extract a new abstract superclass that we would probably call RoadTrip... It doesn't look good...
Here is a refactored version of these class, using composition instead of inheritance:
class Trip { private readonly string _from; private readonly string _to; private readonly ITransportationMode _transportationMode; public string From { get { return _from; } } public string To { get { return _to; } } public Trip(string from, string to, ITransportationMode transportationMode) { _from = from; _to = to; _transportationMode = transportationMode; } public DateTime CalculateEstimatedArrivalTime() { return DateTime.Now.Add(_transportationMode.CalculateTransportationTimeBetween(_from, _to)); } } internal interface ITransportationMode { TimeSpan CalculateTransportationTimeBetween(string from, string to); } class CarTransportationMode : ITransportationMode { public TimeSpan CalculateTransportationTimeBetween(string from, string to) { } } class PlaneTransportationMode : ITransportationMode { public TimeSpan CalculateTransportationTimeBetween(string from, string to) { } }
In a few words, I've extracted the calculations in their own classes that implement an ITransportationMode interface. They are injected in the Trip class through the constructor (but we could also use a setter injection or a method injection).
This implementation improves the code in several way:
- The calculation of the transportation time is done in a new class, it's not polluting our Trip class any more. It's a lot easier to add new algorithms, we just need to add a new implementation of ITransportationMode. It can evolves independently of the Trip class. It's called the separation of concern principle.
- We can more easily configure our Trip class. Before, wa had to create a new instance of a TripBase sub class to change the way the calculation was done, now we can just change the instance of ITransportationMode while keeping the same Trip object.
- We can have several subclasses of Trip using the same algorithm without introducing a new abstract class (such as RoadTrip). Therefore, composition helps us to keep our hierarchy of class flat.
Filed in General development | 12 responses so far
The .Net Frog is also available in french at: