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.

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply