Liskov Substitution Principle

Written by vadim-samokhin | Published 2017/12/11
Tech Story Tags: programming | software-development | solid | oop

TLDRvia the TL;DR App

Or How to Create Beautiful Abbreviations

This is a third post on SOLID principles — check out the Open closed principle if you missed it. Here I start with a short intro on how this concept was forming, and demonstrate a descriptive example (no Square and Rectangle, I promise!).

Bertrand Meyer, 1986

Bertrand Meyer first introduced the concept of contract and implemented it in his language Eiffel. Contract is identified by preconditions, invariants and postconditions. Here is a nice summary from Wikipedia:

1. [A routine can] Expect a certain condition to be guaranteed on entry by any client module that calls it: the routine’s precondition — an obligation for the client, and a benefit for the supplier (the routine itself), as it frees it from having to handle cases outside of the precondition.2. Guarantee a certain property on exit: the routine’s postcondition — an obligation for the supplier, and obviously a benefit (the main benefit of calling the routine) for the client.3. Maintain a certain property, assumed on entry and guaranteed on exit: the class invariant.

There is only one language I’m aware of where contract is a built-in concept — it’s Eiffel itself. In other languages contract can be enforced by method’s signature: type of an arguments, type of return value and an exception that can be thrown. But since it’s hardly feasible to invent a type-system that can be used to enforce all existing business-rules, this approach is quite limited.

Barbara Liskov, 1994

The way Barbara Liskov posed her principle represents a concept of subtyping, or subtype polymorphism. This type of polymorphism is the most common in Object-oriented programming and is usually referred to as simply “polymorphism”. Liskov formalized the Meyer’s approach and it looks way more academically:

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.

The human-readable version repeats pretty much everything that Bertrand Meyer already has said, but it relies totally on a type-system:

1. Preconditions cannot be strengthened in a subtype.2. Postconditions cannot be weakened in a subtype.3. Invariants of the supertype must be preserved in a subtype.

Robert Martin, 1996

Robert Martin made the definition sound more smoothly and concisely:

Functions that use pointers of references to base classes must be able to use objects of derived classes without knowing it.

However, he hasn’t introduced anything new.

Violation of Liskov Substitution Principle

I often find that in order to comprehend some principle it’s important to realize when it’s violated. That’s what I want to do now.

What does the violation of this principle mean? It implies that an object doesn’t fulfill the contract imposed by an abstraction expressed with an interface. Or, in other words, it means that you identified your abstractions wrong.

Consider the following example:

interface Account{/*** Withdraw $money amount from this account.** @param Money $money* @return mixed*/ public function withdraw(Money $money);}

class DefaultAccount implements Account{private $balance;

**public function** withdraw(Money $money)  
{  
    **if** (!$this->enoughMoney($money)) {  
        **return**;  
    }  

    $this->**balance**\->subtract($money);  
}  

}

Is this a violation of LSP? Yes, since Account’s contract tells that an account would be withdrawn, but this is not always the case. So what should I do in order to fix it? I just modify the contract:

interface Account{/*** Withdraw $money amount from this account if its balance is enough.* Otherwise do nothing.** @param Money $money* @return mixed*/ public function withdraw(Money $money);}

Voilà, now the contract is satisfied.

This subtle violation often imposes client with the ability to tell the difference between concrete objects employed. For example, given the first Account’s contract, it could look like the following:

class Client{public function go(Account $account, Money $money){if ($account instanceof DefaultAccount && !$account->hasEnoughMoney($money)) {return;}

    $account->withdraw($money);  
}  

}

And this automatically violates Open closed principle.

This point also addresses a misconception that I encounter quite often about LSP violation. It goes like “if a parent’s behavior changed in a child, than it violates LSP”. It doesn’t — as long as a child doesn’t violate its parent’s contract.

Liskov Substitution Principle in SOLID

My problem with this abbreviation is that if “polymorphism” is present there, why “encapsulation” and “composability” aren’t? Those are in no way less important. Probably it’s because it’s hard to come up with beautiful abbreviations using “e” and “c”? I bet it is.

Wrapping it up

Don’t get me wrong, I like SOLID and the approaches it promotes. But it’s just a shape of deeper principles lying in its foundation. The examples above made it clear what this principle is striving for: it’s loose coupling. In other words, don’t make your clients care what concrete class is in use. Noble goal, but how to achieve it? First, start with decomposing your problem space — domain. Second, express your contract in plain English in a method signature.


Written by vadim-samokhin | Aspiring object-thinker
Published by HackerNoon on 2017/12/11