The Liskov Substitution Principle (LSP)
The original definition reads rather technical:
Let \(\Phi,(x)\) be a property provable about objects \(x\) of type \(T\). Then \(\Phi,(y)\) should be true for objects \(y\) of type \(S\) where \(S\) is a subtype of \(T\).
What is meant by this in the context of object-oriented programming is:
A derived classes should be usable everywhere where its superclass is used.
Simple Example
Again from github.
Bad Practice
class Animal:
def __init__(self, name: str):
self.name = name
def animal_leg_count(animals: list[Animal]):
for animal in animals:
if isinstance(animal, Lion):
print(4)
elif isinstance(animal, Mouse):
print(4)
elif isinstance(animal, Pigeon):
print(2)
class Lion(Animal):
pass
animal_leg_count([Lion("Simba")])
What if someone introduces a new Animal Zebra without extending animal_leg_count()? General Remark: Having to request the instance of a class points towards a violation of the LSP
Good Practise
from abc import ABC,abstractmethod
#
# Example taken from https://gist.github.com/dmmeteo/f630fa04c7a79d3c132b9e9e5d037bfd
#
# "Open for Extension, closed for Modification"
#
class Animal(ABC):
def __init__(self, name: str):
self.name = name
@abstractmethod
def leg_count(self): #
pass
...
class Lion(Animal):
def leg_count(self):
return 4
def animal_leg_count(animals: list):
for animal in animals:
print(animal.leg_count())
animal_leg_count([Lion("Simba")]])
The user now gets a nice error message if the number of legs of a new Animal has not been specified.
Examples @ Linde Engineering
Good Practise
A SMILE_Model_no_deriv is usable anywhere where a SMILE_Model can be used.
Bad Practice @ LE (is it though?))
In SMILE, a specific unit operation is generally derived from a Base class. The Split model in SMILE is defined as derivation from SMILE_Model_Adept.
However, a Split is obviously not usable everywhere where a SMILE_Model_CppAD may be used.
I think this can be considered this a boundary case, as SMILE_Model_CppAD is an abstract base class and hence an interface rather than a class; in contrast Split is a instance which complies to it.
General Remarks
Bad Practise
Deriving from C++ standard containers is generally bad practise.
One sometimes see code such as
struct my_vector : public std::vector<double> {
my_vector(size_t myLen) : std::vector<double>(myLen) {};
};
This is a violation of the LSP, as myvector cannot be everywhere where a std::vector can. For instance, it no longer has a default ctor, which is a quite fundamental property of standard containers.
Hint: C++11 allows to mark a class marked to forbids deriving from it (keyword final) a class and hence violating the LSP. Then, why is std::vector not marked final? The only reason is that that would break too much (bad?) code - see here