Design Principles and Patterns

L in SOLID – Liskov Substitution Principle. Short explanation with example.

Let Φ(x) be a property provable about objects x of type T. Then Φ(y) should be true for objects y of type S where S is a subtype of T.
Barbara Liskov

Sounds so confusing… But really it is not so complicated, just lets first redefine it like this:

Objects of a superclass shall be replaceable with objects of its subclasses without breaking the application. That requires the objects of your subclasses to behave in the same way as the objects of your superclass.

There is a close relationship between this principle and “Design by Contract” which was introduced by Bertrand Mayer:

Using this scheme, methods of classes declare preconditions and postconditions. The preconditions must be true in order for the method to execute. Upon completion, the method guarantees that the postcondition will be true.

Putting it all together we get 3 simple rules which explains the essence:

  • Derived method of a subclass needs to accept the same parameters and validation rules needs to be of the same or lower level of restriction.
  • The return value of derived method needs to be of the same or higher level of restriction.
  • Behaviour of derived method must not break behaviour of parent method.

Why those rules are important? Because:

  • If overriden method would accept smaller range of parametr values, calling class could fail while it expected to pass wider range.
  • The same way just vice versa with returning value. If overiden method would return less restricted value, calling code could fail because of not knowing how to deal with it.
  • If the calling code use derived method of a subclass and expect behaviour which would deliver superclass, would fail if derived method changes logic of parent method.

Example

Lets take our common HR model which we used in other SOLID samples. Imagine there is an employee role Salesman and we need to add another one – SalesmanSupervisor. The main difference for supervisor is formula for calculation of annual income. How would we implement it in either wrong and correct way according to Liskov Substitution Principle?

Wrong


public class Salesman implements Employee {
public in getAnnualIncome() {
return getMonthlySalary() * 12 + getAnnualSalesAmount() * 0.05;
}
}

public class SalesmanSupervisor extends Salesman {
@Override
public in getAnnualIncome() {
return getMonthlySalary() * 12 + getAnnualSalesAmount() * 0.05 + getBonus();
}
}

As we see here, SalesmanSupervisor get additional annual bonus comparing to regular Salesman. And the wrong part here is that getAnnualIncome() of class SalesmanSupervisor cannot be used to get annual income of Salesman. Because calculation simply differs. So correct way would be to implement as separate sibling class which implements the same Employee interface.

Correct


public class Salesman implements Employee {
public in getAnnualIncome() {
return getMonthlySalary() * 12 + getAnnualSalesAmount() * 0.05;
}
}

public class SalesmanSupervisor implements Employee {
public in getAnnualIncome() {
return getMonthlySalary() * 12 + getAnnualSalesAmount() * 0.05 + getBonus();
}
}

Check for other principles of SOLID in this post – SOLID Software Design Principles. Summary.

About Danas Tarnauskas