Favour Composition Over Inheritance
Favour Composition Over Inheritance
The title should read ‘Favour Composition and Aggregation over Inheritance‘, but it would have been too long. There isn’t much difference between Composition and Aggregation. Either one is more flexible than Inheritance.
Inheritance establishes a rigid relationship between a superclass and subclass. I have written about the problems posed by Inheritance here and here.
Let’s explore why Composition/Aggregation should often be the design choice over Inheritance. And there is no better way than with an example.
Say, we are a programmer on an employee payroll application.
There are two types of employees; workers and managers. We will model these as Employee
, Worker
and Manager
classes in the application.
There are differences in how managers and workers get paid. Managers receive a monthly salary payment; their annual salary divided by the number of months. Workers get paid a weekly wage; their hourly rate multiplied by the number of hours worked that week.
How should we design our application given this requirement?
Note: An employee payroll application would typically do much more than paying the employees, but for the sake of brevity, we will keep this example simple.
Inheritance
Classes Worker
and Manager
could inherit from abstract base class Employee
:
Employee
has an abstract method CalcPay()
which both Worker
and Manager
implement:
public class Worker : Employee { /// ... public override decimal CalcPay() { return HourlyRate * WeeklyHoursWorked; } }
public class Manager : Employee { /// ... public override decimal CalcPay() { const int NumberOfMonthsInYear = 12; return AnnualSalary / NumberOfMonthsInYear; } }
When the Employee
is a Manager
, we calculate their monthly salary payment, while when it’s a Worker
, then the code will determine the weekly pay based on their hours worked.
So far, so good. But there is trouble brewing: a legal requirement forces the company to move off their existing model and offer salaries to workers as well as wages to managers. It’s up to us to implement this change in the payroll application.
The new requirement poses a problem: Now that employees may choose between the different payment options, linking how we calculate payment to the type of employee no longer works out. The Inheritance relationship is not flexible enough to handle this need. Unless we carefully redesign our application, we might be forced to put in a hack.
Composition
What if we used Composition instead?
Let’s compose class Employee
from a PaymentType
. PaymentType
comes in two versions: SalaryPayment
and WagePayment
:
Here is the code for SalaryPayment
and WagePayment
:
public class SalaryPayment : PaymentType { private const int NumberOfMonthsInYear = 12; private decimal AnnualSalary { get; set; } public SalaryPayment(decimal annualSalary) { AnnualSalary = annualSalary; } public override decimal CalcPay() { return AnnualSalary / NumberOfMonthsInYear; } } public class WagePayment : PaymentType { private decimal HourlyWage { get; set; } private decimal WeeklyHoursWorked { get; set; } public WagePayment (decimal hourlyWage, decimal weeklyHoursWorked) { HourlyWage = hourlyWage; WeeklyHoursWorked = weeklyHoursWorked; } public override decimal CalcPay() { return HourlyWage * WeeklyHoursWorked ; } }
Class Employee
calls PaymentType
‘s CalcPay()
to determine an employee’s pay:
public abstract class Employee { public PaymentType PaymentType { get; set; } public decimal CalcPay() { return PaymentType.CalcPay() } }
Employee
instances can be independently composed of either a WagePayment
or a SalaryPayment
PaymentType
:
someWorker.PaymentType = new SalaryPayment(78500);
or
someManager.PaymentType = new WagePayment(23.75m, 35);
Unlike with Inheritance, in Composition (and Aggregation) we may construct a class (here: Employee
) of partitions with different implementations. In our case, we were only required to conform to the interface of PaymentType.CalcPay()
. On the other hand, with Inheritance, we were locked into the interface of Employee.CalcPay()
as well as the different implementations of the subclasses, Worker
and Manager
.
Consider the use cases and their likely evolution when choosing Inheritance or Composition/Aggregation as your design choice.
In general, favour Composition/Aggregation over Inheritance.
Leave a Reply
Want to join the discussion?Feel free to contribute!