# The Liskov Substitution Principle (Software Design)

The Liskov Substitution Principle (LSP) governs design rules for object oriented languages and states that "subtypes must be substitutable for their base types." [📖ASD, p. 111]:

If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2, then S is a subtype of T." Barbara Liskov, Data Abstraction and Hierarchy

The Liskov Substitution Principle is one of the SOLID-principles.

## Example​

In languages that conform to the LSP, the effects of this principle can easily be reproduced:

    class A    {        public function itsName()        {            echo static::class;        }    }    class B extends A{}    class C extends B {}    class Client    {        public function process(A $a): B {$a->itsName();            return new B();        }    }

Here, the process-function of Client digests an instance of A, calls a method on it and returns B.

    $client = new Client();$client->process(new A()); // A

According to the LSP, the code must also work if we pass any subtype of A.

    $client = new Client();$client->process(new A()); // A    $client->process(new B()); // B$client->process(new C()); // C

Once we create a specification of Client, let's say ConcreteClient, we must be careful when overwriting process():

class ConcreteClient extends Client{    public function process(A $a): B {}} Here, the argument type and the return type do not change. Any ConcreteClient can be used as a substitute for Client. However, if we narrow the type of the argument down to B, PHP quits with an error message befoe running the script: class ConcreteClient extends Client{ public function process(B$a): B {}}
Fatal error: Declaration of ConcreteClient::process(B $a): B must be compatible with Client::process(A$a): B

### Implications​

The implications are as follows:

• Argument-Types may be widened, and must not be narrowed down (Contravariance)

Contravariance: B < A (B subtype A), A:T, B:T argument types; if A:T < B:T, then T is Contravariant

At this point, we are not allowed to narrow the argument-type passed to process() down. This is because the parent implementation Client::process() sets up an interface all subtypes have to conform to: It seems logical at first to be allowed to pass an B for an A, since B is an A, but any program doing so would break as soon as ConcreteClient::process() accesses a field only known to B, and an A is passed instead. Thus, narrowing down is not allowed with the given example.

Figure 1 With Contravariance, the inheritance hierarchy of argument types must be in opposite direction to the inheritance of the method's classes they're used in.
• Return-Types may be narrowed down, and must not be widened (Covariance)

Covariance: B < A (B subtype A), B:T, A:T return types; if B:T < A:T, then T is Covariant

Conversely, return types must not be widened. Given our example, any program querying Client::process() expects an B. If that return type would be widened to A in ConcreteClient::process(), any program that tries to access a field specific to B would break if ConcreteClient::process() instead returns an A - which is in fact unaware of B.

Figure 2 With Covariance, the inheritance hierarchy of return types must be in the same direction to the inheritance of the method's classes they're used for.