## 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.* ![Alt text](lsp_cartoon.png) ## Simple Example Again from [github](https://gist.github.com/dmmeteo/f630fa04c7a79d3c132b9e9e5d037bfd). ### Bad Practice ```py 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 ```py 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*](https://dev.azure.com/LindeEngineering/ITP-Smile/_git/M-SMILE?path=/include/SMILE_Model_no_deriv.hpp) is usable anywhere where a [*SMILE_Model*](https://dev.azure.com/LindeEngineering/ITP-Smile/_git/M-SMILE?path=/include/SMILE_Model.hpp) can be used. ### Bad Practice @ LE (is it though?)) In SMILE, a specific unit operation is generally derived from a Base class. The [*Split*](https://dev.azure.com/LindeEngineering/ITP-Smile/_git/M-SMILE?path=/Model_Repository/Model_Repository/Units/Split.ADEPT.hpp) 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 ```c++ struct my_vector : public std::vector { my_vector(size_t myLen) : std::vector(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](https://en.cppreference.com/w/cpp/language/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](https://stackoverflow.com/questions/9298450/are-c11-standard-containers-final) ## Further Reading * [What is an example of the liskov substitution principle?](https://stackoverflow.com/questions/56860/what-is-an-example-of-the-liskov-substitution-principle) ![Alt text](lsp_cartoon_2.png)