Agile Software Development# 10.LSP: The Liskov Substitution Principle

10.LSP: The Liskov Substitution Principle

SUBTYPES MUST BE SUBSTITUTABLE FOR THEIR BASE TYPES.

📌Barbara Liskov wrote this in 1988:

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.

image-20220204145107917

 

📌What does LSP solve?

OCP demonstrates inheritance is IMPORTANT.

LSP will introduce how to best use inheritance.

 

📌Example of a Violation of the LSP

A violation of LSP causing a violation of OCP!⚠

Apparently, the DrawShape function is trying to use LSP which takes Shape as an argument. But it failed, and it is violating LSP and thus violating OCP as well... When a new shape comes in, we have to modify the DrawShape once again...

 

📌Potential problem of LSP and its solution

Problem

Since LSP asks for a BaseClass and DerivedClass relationship, a.k.a. IS-A relationship. Sometime, this kind of relationship would mislead ourself.

For example, we are known with common sense, Square is a Rectangle. Therefore, square is a subtype of rectangle.

However, SetWidth , SetHeight, itsWidth, and itsHeight are redundant information since square is 1:1 size.

Drawback

  • Memory Waste: Considering in a CAD/CAE product, millions of squares take redundant bits will cause memory waste.
  • Confused API: These functions are inappropriate since width and height of a square are identical.

Solution

The solution is obvious. We could override the SetWidth and SetHeight function.

 

📌Who is the boss of LSP?

The clients.

If we see it from the creator of Square and Rectangle

Everything seems pretty good so far.

If we see it from others, possible clients themselves

The test function will fail if we pass a Square inside.

Therefore, there is nothing wrong of current design. It only smells when it encounters such situation. Therefore, the boss of LSP should only be expressed in terms of clients.

 

📌DBC - Design by Contract

Why DBC?

Since LSP is quite unpredictable and unquantified, developers refer to DBC - design by contract.

What is DBC?

The contract of that class is explicitly stated.

What does it state?

  • precondition, must be true in order for the method to execute.
  • postcondition, which is guaranteed to be true by method.

Example

precondition

method

postcondition

 

📌DBC meets changes

Rules

The precondition of derived class: can be stronger or normal ()

The postcondition of derived class: can be weaker or normal ()

Example

Square is derived class of Rectangle.

The postcondition of Rectangle is assert((itsWidth == w) && (itsHeight == old.itsHeight));

The postcondition of Square is unset right now. Therefore, it is weaker.⚠❌

Therefore, it violates the LSP.

 

📌Example - Customized Set

✏Problem1

A commercial 3rd party library has container classes, e.g. Set. The Set has 2 following versions:

  • BoundedSet, similar to array with fixed size memory allocation
  • UnboundedSet, similar to dynamic array with no limit on the amount of elements

The author may 1️⃣in the future replace such class into a more appropriate and efficient container class.

🔨Solution1

Regarding the first problem, use ADAPTER method.

image-20220205173903046

By using the preceding interface, the client code needn't to care which set it used. They only focus on Add, Delete, etc.

✏Problem 2

Meanwhile, part of the 3rd party class 2️⃣doesn't support template programming. For example, if I want to use PersistentSet, I have to register PersistentObject first.

🔨Solution2

Therefore, we can take the following strategy - delegate the process.

image-20220205175624340

⚒Solution1+Solution2 = All In One

It is to combine everything into a compact system.

image-20220205212122663

 

📌Example - Line and LineSegment

The Line here is a mathematical line which can extend to infinity.

The LineSegment here is a geometrical line whose length can be measured.

image-20220205215244547

Problem

With common sense, the LineSegment is a derived class of Line.

The LineSegment is a derivative of Line. The IsOn() method is set to virtual and everything seems ok. But if I do this on LineSegment will fail⚠❌:

image-20220207141307252

Apparently, the intersection point(red🔴) of LineSegment may be not on itself.

Solution

Therefore, we could see a division here. Line cannot be the base class of LineSegment. Then the strategy shifts to segregate the Line into a LinearObject class.

 

 

📌Rules learned from previous example⭐⭐⭐

We can state that if a set of classes all support a common responsibility, they should inherit that responsibility from a common superclass. If a common superclass does not already exist, create one, and move the common responsibilities to it.

 

📌Heuristics and Conventions

A derivative that does less than its base is usually not substitutable for that base, and therefore violates the LSP. Base class should do more than derived class.

 

📌What is Degenerate function?

A function in Base Class while it won't be used in Derived Class.

Previous
Previous

Essential C++ # 01. Basic C++ Programming

Next
Next

Agile Software Development# 9.OCP: The Open-Closed Principle