Favor Composition Over Inheritance Part 2 | Source: Coding Delight
And here is the second part of the article
Yesterday I wrote part one of my two-part series on why we should favor the technique of composition over inheritance. I began by looking at how a purely inheritance-based model quickly becomes unworkable as the properties and methods of base classes often lead to an inflexible design. Today, by contrast, I will look at solving the same problem by the use of composition. Specifically, we will look at how using interfaces for composition in C# allows for a highly flexible design.
The problem posed yesterday was to model the behavior of a car when a driver applies changes to it. I want to be able to track the angle of the wheels and the speed at which the wheels have been turned by the engine. The first car to model is the Toyota Corolla.
Yesterday, the first class that was designed was the BaseCar class, which contained a number of abstract methods which defined our behavior. It also contained our wheels. Today, instead, I am going to look at the behaviors of the car. It seems to me that there are two separate behaviors which are independent of each other. There is steering and there is driving (or accelerating). These behaviors will form the basis for our interface design. A car also has a manufacturer, so we will create an interface for that too.
interface IDriving
{
void Accelerate(double kph);
}interface ISteering
{
void TurnLeft(double degrees);
void TurnRight(double degrees);
}interface IManufacturer
{
string Name { get; }
}
There are a couple of ways that we could look at designing the car interface. We could say that an ICar inherits from both ISteering and IDriving, but instead I think we should say that an ICar has both an ISteering and IDriving. This seems to make the most sense.
interface ICar
{
IWheel FrontLeft { get; }
IWheel FrontRight { get; }
IWheel RearLeft { get; }
IWheel RearRight { get; }
ISteering Steering { get; }
IDriving Driving { get; }
IManufacturer Manufacturer { get; }
}
Already we can see that simply by thinking about the components and how they logically relate to each other, we have created a different design from before. The interfaces help us think more abstractly than base classes would otherwise had. Now that we have designed our interfaces (and the astute reader might have noticed that the Wheel class has now become an IWheel interface too, though defining either is beyond the scope of these articles) we can get started on defining the functionality of our classes.
First we will create a TwoWheelDrive class which implements the IDriving interface.
class TwoWheelDrive : IDriving
{
private readonly IWheel left;
private readonly IWheel right;
public TwoWheelDrive(IWheel left, IWheel right)
{
this.left = left;
this.right = right;
}
public void Accelerate(double kph)
{
this.left.RotationSpeed += kph;
this.right.RotationSpeed += kph;
}
}
Immediately it can be seen that this class can be used for both types of two-wheel drive car — front or rear. All we will have to do is pass either the front or rear wheels to it. Next up we’ll implement the two-wheel steering functionality in much the same way. Note that in this case, the steering class has to be a “front” steering class or a “rear” steering class, as each type of steering requires the wheels to turn in opposite directions to achieve the same outcome for the driver.
class FrontSteering : ISteering
{
private readonly IWheel frontLeft;
private readonly IWheel frontRight;
public FrontSteering(IWheel frontLeft, IWheel frontRight)
{
this.frontLeft = frontLeft;
this.frontRight = frontRight;
}
public void TurnLeft(double degrees)
{
this.frontLeft.Angle -= degrees;
this.frontRight.Angle -= degrees;
}
public void TurnRight(double degrees)
{
this.frontLeft.Angle += degrees;
this.frontRight.Angle += degrees;
}
}
And next, my Toyota manufacturer class. I’ve implemented it as a singleton because that will be sufficient for this problem.
class Toyota : IManufacturer
{
private static IManufacturer instance;
private Toyota() { }
public string Name { get { return "Toyota"; } }
public static IManufacturer GetInstance()
{
return instance ?? (instance = new Toyota());
}
}
Finally, I can create my ToyotaCorolla class.
class ToyotaCorolla : ICar
{
public ToyotaCorolla()
{
this.FrontLeft = new Wheel();
this.FrontRight = new Wheel();
this.RearLeft = new Wheel();
this.RearRight = new Wheel(0, 0);
this.Steering = new FrontSteering(this.FrontLeft, this.FrontRight);
this.Driving = new TwoWheelDrive(this.FrontLeft, this.FrontRight);
this.Manufacturer = Toyota.GetInstance();
}
public IWheel FrontLeft { get; private set; }
public IWheel FrontRight { get; private set; }
public IWheel RearLeft { get; private set; }
public IWheel RearRight { get; private set; }
public ISteering Steering { get; private set; }
public IDriving Driving { get; private set; }
public IManufacturer Manufacturer { get; private set; }
}
Now when the customer comes along and asks for the rear wheel drive sports edition, I can create the following class for them.
class ToyotaCorollaSports : ICar
{
public ToyotaCorollaSports()
{
this.FrontLeft = new Wheel();
this.FrontRight = new Wheel();
this.RearLeft = new Wheel();
this.RearRight = new Wheel(0, 0);
this.Steering = new FrontSteering(this.FrontLeft, this.FrontRight);
this.Driving = new TwoWheelDrive(this.RearLeft, this.RearRight);
this.Manufacturer = Toyota.GetInstance();
}
public IWheel FrontLeft { get; private set; }
public IWheel FrontRight { get; private set; }
public IWheel RearLeft { get; private set; }
public IWheel RearRight { get; private set; }
public ISteering Steering { get; private set; }
public IDriving Driving { get; private set; }
public IManufacturer Manufacturer { get; private set; }
}
In fact, it is at this point that you can quite easily see that the only difference between ToyotaCorollas lies in the parameters that are passed into the driving constructor. A pattern is emerging. We now have the ability to do away entirely with our ToyotaCorolla class. The only difference is in our constructor parameters. What I can do instead is refactor my code and use constructor parameters to define our classes.
class Car : ICar
{
public Car(IWheel frontLeft, IWheel frontRight, IWheel rearLeft,
IWheel rearRight, ISteering steering, IDriving driving,
IManufacturer manufacturer, string carName)
{
this.FrontLeft = frontLeft;
this.FrontRight = frontRight;
this.RearLeft = rearLeft;
this.RearRight = rearRight;
this.Steering = steering;
this.Driving = driving;
this.Manufacturer = manufacturer;
this.Name = carName;
}
public IWheel FrontLeft { get; private set; }
public IWheel FrontRight { get; private set; }
public IWheel RearLeft { get; private set; }
public IWheel RearRight { get; private set; }
public ISteering Steering { get; private set; }
public IDriving Driving { get; private set; }
public IManufacturer Manufacturer { get; private set; }
public string Name { get; private set; }
}static class CarFactory
{
public static ICar ToyotaCorolla()
{
IWheel frontLeft = new Wheel();
IWheel frontRight = new Wheel();
return new Car(frontLeft, frontRight, new Wheel(), new Wheel(),
new FrontSteering(frontLeft, frontRight),
new TwoWheelDrive(frontLeft, frontRight), Toyota.GetInstance(),
"Corolla");
}
public static ICar ToyotaCorollaSports()
{
IWheel frontLeft = new Wheel();
IWheel frontRight = new Wheel();
IWheel rearLeft = new Wheel();
IWheel rearRight = new Wheel();
return new Car(frontLeft, frontRight, rearLeft, rearRight,
new FrontSteering(frontLeft, frontRight),
new TwoWheelDrive(rearLeft, rearRight), Toyota.GetInstance(),
"Corolla Sports");
}
}
So as you can see, by using composition we have created a much more flexible design. We can reuse the bits that make sense to be reused and ignore the bits that don’t. The interfaces have helped us think abstractly and separated out how the objects relate to each other from how the objects work. We can use a car without knowing how the steering is implemented or whether it is a front, rear or four-wheel drive. We no longer have a complicated object hierarchy and adding new car designs takes a little effort. When it comes time to design a car with four-wheel drive, all we need to do is create a four-wheel drive class and a factory method.
Far too many professional developers think in an “is-a” mindset when they want to reuse code. I hope that I have sufficiently demonstrated that composition helps us reuse code far more efficiently and with a lot less complexity than using inheritance. As always, leave a comment, I would love to hear your feedback!
I am taking two weeks holiday, after which I will be (hopefully) blogging on a regular basis.
Source: Favor Composition Over Inheritance part 2 | Coding Delight
Originally published at The Azure Coder.