Liskov Substitution Principle: How to Create Beautiful Abbreviations
Want to learn more about the Liskov Substitution Principle? Check out this tutorial to learn more about creating abbreviations using SOLID principles and the LSP.
Join the DZone community and get the full member experience.
Join For FreeThis is the third post on SOLID principles — check out the open-closed principle if you missed it. Here, I will get started with a short intro on how this concept was formed and provide 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. The 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.
Eiffel is the only language that I’m aware of where a contract is a built-in concept. In other languages, contracts can be enforced by a method’s signature, including the type of arguments, type of return values, 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 explained 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 Meyer’s approach, and it looks more academic:
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 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 more concise:
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 the Liskov Substitution Principle
I often find that, in order to comprehend some principles, it’s important to realize when it’s been violated. This is what I will 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. 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. This is because the account’s contract tells us 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 the open-closed principle. This point also addresses a misconception that I encounter quite often about LSP violation. It says the “if a parent’s behavior changed in a child, then, it violates LSP.” However, 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 are “encapsulation” and “composability” not? 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 — to loose coupling. In other words, don’t make your clients care what concrete class is in use. While this is a noble goal, how do we achieve it? First, start with decomposing your problem space domain. Second, express your contract in a method signature using plain English.
Published at DZone with permission of Vadim Samokhin. See the original article here.
Opinions expressed by DZone contributors are their own.
Trending
-
Avoiding Pitfalls With Java Optional: Common Mistakes and How To Fix Them [Video]
-
Writing a Vector Database in a Week in Rust
-
How To Approach Java, Databases, and SQL [Video]
-
Structured Logging
Comments