Borys Serebrov

<!doctype html><html lang=en><head><meta charset=utf-8><meta name=theme-color content=”#44ccff”><meta name=twitter:card content=”summary”>OOP SOLID Principles "L" - Liskov Substitution Principle • vim, git, aws and other three-letter words<link rel=canonical href=https://serebrov.github.io/html/2016-02-18-oop-solid-l-liskov-substitution-principle.md><link rel=icon href=/favicon.ico><link rel=stylesheet href=/assets/css/main.6a060eb7.css><link rel=stylesheet href=/css/custom.css><script type=application/javascript>var doNotTrack=!1;doNotTrack||(window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)},ga.l=+new Date,ga(“create”,”UA-58056088-1”,”auto”),ga(“send”,”pageview”))</script><script async src=https://www.google-analytics.com/analytics.js></script></head><body class='page type-post has-sidebar'><div class=site><div id=sidebar class=sidebar><a class=screen-reader-text href=#main-menu>Skip to Main Menu</a><div class=container><section class='widget widget-about sep-after'><header><div class=logo><a href=/><img src=/images/logo.jpg></a></div><h2 class='title site-title'><a href=/>vim, git, aws and other three-letter words</a></h2><div class=desc>Software Development Notes</div></header></section><section class='widget widget-search sep-after'><header><h4 class='title widget-title'>Search</h4></header><form action=/search id=search-form class=search-form></form></section><section class='widget widget-sidebar_menu sep-after'><nav id=sidebar-menu class=’menu sidebar-menu’ aria-label=’Sidebar Menu’><div class=container><ul><li class=item><a href=/>Home</a></li><li class='item has-current'><a href=/posts/>Posts</a></li><li class=item><a href=/archive/>Archive</a></li></ul></div></nav></section><section class='widget widget-taxonomy_cloud sep-after'><header><h4 class='title widget-title'>Tags</h4></header><div class='container list-container'><ul class='list taxonomy-cloud no-shuffle'><li><a href=/tags/ai/ style=font-size:1em>ai</a></li><li><a href=/tags/android/ style=font-size:1em>android</a></li><li><a href=/tags/angularjs/ style=font-size:1.1em>angularjs</a></li><li><a href=/tags/aws/ style=font-size:1.9em>aws</a></li><li><a href=/tags/bash/ style=font-size:1em>bash</a></li><li><a href=/tags/celery/ style=font-size:1em>celery</a></li><li><a href=/tags/chrome/ style=font-size:1em>chrome</a></li><li><a href=/tags/cmd/ style=font-size:1em>cmd</a></li><li><a href=/tags/cors/ style=font-size:1em>cors</a></li><li><a href=/tags/cw-logs/ style=font-size:1.05em>cw-logs</a></li><li><a href=/tags/disqus/ style=font-size:1em>disqus</a></li><li><a href=/tags/docker/ style=font-size:1.05em>docker</a></li><li><a href=/tags/drone/ style=font-size:1em>drone</a></li><li><a href=/tags/dynamodb/ style=font-size:1.1em>dynamodb</a></li><li><a href=/tags/eb/ style=font-size:1.2em>eb</a></li><li><a href=/tags/ejs/ style=font-size:1em>ejs</a></li><li><a href=/tags/emr/ style=font-size:1.05em>emr</a></li><li><a href=/tags/express.js/ style=font-size:1em>express.js</a></li><li><a href=/tags/fastapi/ style=font-size:1em>fastapi</a></li><li><a href=/tags/git/ style=font-size:2em>git</a></li><li><a href=/tags/google-colab/ style=font-size:1em>google-colab</a></li><li><a href=/tags/hive/ style=font-size:1em>hive</a></li><li><a href=/tags/jquery/ style=font-size:1.1em>jquery</a></li><li><a href=/tags/js/ style=font-size:1.1em>js</a></li><li><a href=/tags/json/ style=font-size:1em>json</a></li><li><a href=/tags/kbd/ style=font-size:1.1em>kbd</a></li><li><a href=/tags/linux/ style=font-size:1em>linux</a></li><li><a href=/tags/mongodb/ style=font-size:1em>mongodb</a></li><li><a href=/tags/mysql/ style=font-size:1.05em>mysql</a></li><li><a href=/tags/node.js/ style=font-size:1.2em>node.js</a></li><li><a href=/tags/npm/ style=font-size:1em>npm</a></li><li><a href=/tags/oauth/ style=font-size:1em>oauth</a></li><li><a href=/tags/oop/ style=font-size:1em>oop</a></li><li><a href=/tags/php/ style=font-size:1.15em>php</a></li><li><a href=/tags/postgresql/ style=font-size:1em>postgresql</a></li><li><a href=/tags/python/ style=font-size:1.1em>python</a></li><li><a href=/tags/rds/ style=font-size:1.05em>rds</a></li><li><a href=/tags/scaleway/ style=font-size:1em>scaleway</a></li><li><a href=/tags/selenium/ style=font-size:1.3em>selenium</a></li><li><a href=/tags/ssh/ style=font-size:1em>ssh</a></li><li><a href=/tags/tmux/ style=font-size:1em>tmux</a></li><li><a href=/tags/tts/ style=font-size:1em>tts</a></li><li><a href=/tags/typing/ style=font-size:1.05em>typing</a></li><li><a href=/tags/vim/ style=font-size:1.05em>vim</a></li><li><a href=/tags/vr/ style=font-size:1em>vr</a></li><li><a href=/tags/vue/ style=font-size:1.1em>vue</a></li><li><a href=/tags/web/ style=font-size:1em>web</a></li><li><a href=/tags/yii/ style=font-size:1.15em>yii</a></li><li><a href=/tags/zeromq/ style=font-size:1em>zeromq</a></li></ul></div></section></div><div class=sidebar-overlay></div></div><div class=main><nav id=main-menu class=’menu main-menu’ aria-label=’Main Menu’><div class=container><a class=screen-reader-text href=#content>Skip to Content</a> <button id=sidebar-toggler class=sidebar-toggler aria-controls=sidebar> <span class=screen-reader-text>Toggle Sidebar</span> </button><ul><li class=item><a href=/>Home</a></li><li class='item current'><a aria-current=page href=/posts/>Posts</a></li><li class=item><a href=/archive/>Archive</a></li></ul></div></nav><div class=header-widgets><div class=container></div></div><header id=header class='header site-header'><div class='container sep-after'><div class=header-info><p class='site-title title'>vim, git, aws and other three-letter words</p><p class='desc site-desc'>Software Development Notes</p></div></div></header><main id=content><article lang=en class=entry><header class='header entry-header'><div class='container sep-after'><div class=header-info><h1 class=title>OOP SOLID Principles “L” - Liskov Substitution Principle</h1></div><div class=entry-meta><span class=posted-on><span class=screen-reader-text>Posted on</span> <time class=entry-date datetime=2016-02-18T00:00:00Z>2016, Feb 18</time></span> <span class=reading-time>9 mins read</span></div></div></header><div class="container entry-content custom"><p>According to the <a href=https://en.wikipedia.org/wiki/Liskov_substitution_principle>Wikipedia</a> the Liskov Substitution Principle (LSP) is defined as:</p><pre tabindex=0>Subtype Requirement: Let f(x) be a property provable about objects x of type T. Then f(y) should be true for objects y of type S where S is a subtype of T. </pre><p>The basic idea - if you have an object of type T then you can also use objects of its subclasses instead of it.</p><p>Or, in other words: the subclass should behave the same way as the base class. It can add some new features on top of the base class (that’s the purpose of inheritance, right?), but it can not break expectations about the base class behavior.</p><p>The expectations about the base class can include:</p><ul><li>input parameters for class methods</li><li>returned values of the class methods</li><li>exceptions are thrown by the class methods</li><li>how method calls change the object state</li><li>other expectations about what the object does and how</li></ul><p>Some of these expectations can be enforced by the programming language, but some of them can only be expressed as the documentation.</p><p>This way to follow the LSP it is not only important to follow the coding rules, but also to use common sense and do not use the inheritance to turn the class into something completely different.</p><p>Let’s see what rules do we need to follow in the code.</p><h1 id=methods-signature-requirements>Methods Signature Requirements</h1><p>Signature requirements are requirements for input argument types and return type of the class methods.</p><p>Let’s imagine we have following class hierarchy:</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-text data-lang=text><span style=display:flex> .————. .————. .————-. </span><span style=display:flex> | LiveBeing | | Animal |<|——–| Cat | </span><span style=display:flex> |————|<|——–|————| |————-| </span><span style=display:flex> | + breeze() | | + eat() |<|—. | + mew() | </span><span style=display:flex> '————' '————' | '————-' </span><span style=display:flex> | </span><span style=display:flex> | .————. </span><span style=display:flex> | | Dog | </span><span style=display:flex> '—–|————| </span><span style=display:flex> | + bark() | </span><span style=display:flex> '————' </span></code></pre></div><p>Here the LiveBeing is the base class which is inherited by Animal which in turn is inherited by Cat and Dog.</p><p>I will use this hierarchy to explain the signature rules.</p><h2 id=covariance-parent—child—-of-return-types-in-the-subtype>Covariance (Parent -> Child -> &mldr;) of return types in the subtype</h2><p>This rule means that the child class can override a method to return a more specific type (Cat instead of Animal).</p><p>Of course, it can return the same type, but it can not return more generic type (like LiveBeing instead of Animal) and it can not return a completely different type (House instead of Animal).</p><p>This rule is easy to understand and it feels natural. Here is an example in pseudo-code:</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-python data-lang=python><span style=display:flex> </span><span style=display:flex><span style=color:#66d9ef>class <span style=color:#a6e22e>Owner</span> </span></span><span style=display:flex> Animal findPet() </span><span style=display:flex> <span style=color:#66d9ef>return new Animal() </span></span><span style=display:flex> </span><span style=display:flex><span style=color:#66d9ef>class <span style=color:#a6e22e>CatOwner</span> extends Owner </span></span><span style=display:flex> Cat findPet() </span><span style=display:flex> <span style=color:#75715e># Covariance - subclass returns more specific type </span></span><span style=display:flex> <span style=color:#66d9ef>return new Cat() </span></span><span style=display:flex> </span><span style=display:flex><span style=color:#66d9ef>class <span style=color:#a6e22e>BadOwner</span> extends Owner </span></span><span style=display:flex> LiveBeing findPet() </span><span style=display:flex> <span style=color:#75715e># Contravariance - breaks the rule and returns more generic type </span></span><span style=display:flex> <span style=color:#66d9ef>return new LiveBeing() </span></span><span style=display:flex> </span><span style=display:flex>function doAction(Owner owner) </span><span style=display:flex> <span style=color:#75715e># OK for Owner, we can put an Animal object into the animal variable. </span></span><span style=display:flex> <span style=color:#75715e># OK for CatOwner, we can put a Cat object into the animal variable. </span></span><span style=display:flex> <span style=color:#75715e># Problem for a BadOwner object, a LiveBeing object can not use be used </span></span><span style=display:flex> <span style=color:#75715e># the same way as Animal object. </span></span><span style=display:flex> Animal animal <span style=color:#f92672>= owner<span style=color:#f92672>-></span>findPet(); </span></span><span style=display:flex> amimal<span style=color:#f92672>->eat(); </span></span></code></pre></div><p>The doAction function demonstrates a possible use case. It is OK if owner is a CatOwner, because both Animal and Cat should behave the same.</p><p>But the BadOwner returns a LiveBeing and it is a problem. There is no guarantee that LiveBeing object behaves the same as Animal.</p><p>For example, if we call animal->eat() this will not work for the LiveBeing (it doesn’t have such a method).</p><h2 id=contravariance-child—parent—-of-method-arguments-in-the-subtype>Contravariance (Child -> Parent -> &mldr;) of method arguments in the subtype</h2><p>This means that a child class can override the method to accept a more generic argument type than the method in the base class (like accept the LiveBeing instead of Animal).</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-python data-lang=python><span style=display:flex><span style=color:#66d9ef>class <span style=color:#a6e22e>Owner</span> </span></span><span style=display:flex> void feed(Animal animal) </span><span style=display:flex> <span style=color:#f92672>… </span></span><span style=display:flex> </span><span style=display:flex><span style=color:#66d9ef>class <span style=color:#a6e22e>GoodOwner</span> extends Owner </span></span><span style=display:flex> <span style=color:#75715e># Contravariance - subclass accepts more generic type </span></span><span style=display:flex> void feed(LiveBeing being) </span><span style=display:flex> <span style=color:#f92672>… </span></span><span style=display:flex> </span><span style=display:flex><span style=color:#66d9ef>class <span style=color:#a6e22e>BadCatOwner</span> extends Owner </span></span><span style=display:flex> void feed(Cat cat) </span><span style=display:flex> <span style=color:#f92672>… </span></span><span style=display:flex> </span><span style=display:flex>function doAction(Owner owner) </span><span style=display:flex> owner<span style=color:#f92672>->feed(new Dog) <span style=color:#75715e># OK for Owner, he accepts any Animal, including the Dog</span> </span></span><span style=display:flex> <span style=color:#75715e># OK for GoodOwner, he accepts any LiveBeing, including the Dog </span></span><span style=display:flex> <span style=color:#75715e># Problem for CatOwner, he doesn't expect the Dog </span></span></code></pre></div><p>In practice, it may feel tempting to break this rule and define the class like BadCatOwner above.</p><p>But, as we can see, the BadCatOwner breaks LSP and we can not use it in the same case where we can use the Owner object.</p><p>Note that although using the more generic type in the subclass is OK in terms of method signature, it may be problematic logically:</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-python data-lang=python><span style=display:flex><span style=color:#66d9ef>class <span style=color:#a6e22e>Owner</span> </span></span><span style=display:flex> void feed(Animal animal) </span><span style=display:flex> animal<span style=color:#f92672>->eat(this<span style=color:#f92672>-></span>findFood()); </span></span><span style=display:flex> </span><span style=display:flex><span style=color:#66d9ef>class <span style=color:#a6e22e>GoodOwner</span> extends Owner </span></span><span style=display:flex> void feed(LiveBeing being) </span><span style=display:flex> <span style=color:#75715e># Problem: LiveBeing doesn't have the eat method </span></span><span style=display:flex> being<span style=color:#f92672>->eat(this<span style=color:#f92672>-></span>findFood()); </span></span></code></pre></div><p>There is a problem here - the GoodOnwer::feed can not call the being->eat() method because LiveBeing doesn’t have the eat method.</p><p>And this way, GoodOwner also can not just forward the execution to the parent method with something like parent::feed(being).</p><p>By the way, if the method doesn’t use parent implementation, it may <a href=http://stackoverflow.com/q/35070912/4612064>indicate the LSP violation</a> - potentially we can have a different behavior for this subtype than in the parent class.</p><h2 id=exceptions-should-be-same-or-subtypes-of-the-base-method-exceptions>Exceptions should be same or subtypes of the base method exceptions</h2><p>No new exceptions should be thrown by methods of the subtype, except where those exceptions are themselves subtypes of exceptions thrown by the methods of the parent type.</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-python data-lang=python><span style=display:flex><span style=color:#66d9ef>class <span style=color:#a6e22e>BadFoodException</span> </span></span><span style=display:flex> </span><span style=display:flex><span style=color:#66d9ef>class <span style=color:#a6e22e>BadCatFoodException</span> extends BadFoodException </span></span><span style=display:flex> </span><span style=display:flex><span style=color:#66d9ef>class <span style=color:#a6e22e>LowQualityFoodException</span> </span></span><span style=display:flex> </span><span style=display:flex> </span><span style=display:flex><span style=color:#66d9ef>class <span style=color:#a6e22e>Owner</span> </span></span><span style=display:flex> void feed(Animal animal, Food food) </span><span style=display:flex> <span style=color:#66d9ef>if (<span style=color:#f92672>not</span> this<span style=color:#f92672>-></span>isGoodFood(food)) </span></span><span style=display:flex> throw BadFoodException() </span><span style=display:flex> <span style=color:#f92672>… </span></span><span style=display:flex> </span><span style=display:flex><span style=color:#66d9ef>class <span style=color:#a6e22e>BadOwner</span> extends Owner </span></span><span style=display:flex> void feed(Animal animal, Food food) </span><span style=display:flex> <span style=color:#66d9ef>if (<span style=color:#f92672>not</span> this<span style=color:#f92672>-></span>isHighQualityFood(food)) </span></span><span style=display:flex> throw LowQualityFoodException() </span><span style=display:flex> <span style=color:#f92672>… </span></span><span style=display:flex> </span><span style=display:flex>function doAction(Owner owner) </span><span style=display:flex> <span style=color:#66d9ef>try </span></span><span style=display:flex> owner<span style=color:#f92672>->feed(new Dog, new SomeFood) </span></span><span style=display:flex> catch (BadFoodException error) </span><span style=display:flex> <span style=color:#75715e># OK for Owner, it can raise BadFoodException </span></span><span style=display:flex> <span style=color:#75715e># OK for CatOwner, it can raise BadCatFoodException (subclass of BadFoodException) </span></span><span style=display:flex> <span style=color:#75715e># Problem for BadOwner, it can raise LowQualityFoodException and it will not be </span></span><span style=display:flex> <span style=color:#75715e># caught here </span></span><span style=display:flex> <span style=color:#f92672>… </span></span></code></pre></div><p>If we don’t follow the rule about exception types, the client code written for the base class Owner will fail for the subclass BadOwner and this way we violate the LSP.</p><h1 id=inheritance-requirements>Inheritance requirements</h1><p>These requirements describe additional rules for inherited methods related to the <a href=https://en.wikipedia.org/wiki/Design_by_contract>Design by contract</a> concept. It defines the “contract” for each method which includes preconditions, postconditions and invariants:</p><ul><li><a href=https://en.wikipedia.org/wiki/Precondition>Precondition</a> is a condition or predicate that must always be true just prior to the execution of some section of code.</li><li><a href=https://en.wikipedia.org/wiki/Postcondition>Postcondition</a> is a condition or predicate that must always be true just after the execution of some section of code</li><li><a href=https://en.wikipedia.org/wiki/Invariant_(computer_science)>Invariant</a> is a condition that can be relied upon to be true during execution of a program, or during some portion of it. For example, a loop invariant is a condition that is true at the beginning and end of every execution of a loop.</li></ul><h2 id=preconditions-cannot-be-strengthened-in-a-subtype>Preconditions cannot be strengthened in a subtype</h2><p>In most cases preconditions are expectations about method input arguments, also an object’s internal state can be a part of the precondition.</p><p>This is a more generic rule of the contravariance rule for method arguments. The contravariance rule says that subclass can accept more generic argument type (LiveBeing instead of Animal), this is a weaker precondition (subclass accepts a wider range of arguments).</p><p>The same logic applies not only to the types of arguments but to the other kind of expectations as well, such as a range of the integer argument:</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-python data-lang=python><span style=display:flex><span style=color:#66d9ef>class <span style=color:#a6e22e>The24Hours</span> </span></span><span style=display:flex> void setHour(int hour) </span><span style=display:flex> <span style=color:#75715e># hour should be between 0 and 23 </span></span><span style=display:flex> <span style=color:#66d9ef>assert (<span style=color:#ae81ff>0</span> <span style=color:#f92672><=</span> hour <span style=color:#f92672>and</span> hour <span style=color:#f92672><=</span><span style=color:#ae81ff>23</span>) </span></span><span style=display:flex> <span style=color:#f92672>… </span></span><span style=display:flex> </span><span style=display:flex><span style=color:#66d9ef>class <span style=color:#a6e22e>TheDay</span> extends The24Hours </span></span><span style=display:flex> void setHour (int hour) </span><span style=display:flex> <span style=color:#75715e># breaks the rule and strengthens the precondition </span></span><span style=display:flex> <span style=color:#75715e># day hour should be between 8 and 16 </span></span><span style=display:flex> <span style=color:#66d9ef>assert (<span style=color:#ae81ff>8</span> <span style=color:#f92672><=</span> hour <span style=color:#f92672>and</span> hour <span style=color:#f92672><=</span> <span style=color:#ae81ff>16</span>) </span></span><span style=display:flex> <span style=color:#f92672>… </span></span><span style=display:flex> </span><span style=display:flex>function doAction(The24Hours hours) </span><span style=display:flex> hours<span style=color:#f92672>->setHour(<span style=color:#ae81ff>3</span>); <span style=color:#75715e># OK for The24Hours object</span> </span></span><span style=display:flex> <span style=color:#75715e># Problem for TheDay - it will raise an error </span></span></code></pre></div><p>So the stronger precondition in the child class broke the client code which worked for the parent class.</p><p>At the same time, if we make the precondition weaker (or even remove it), the client code will work the same way as for parent class.</p><h2 id=postconditions-cannot-be-weakened-in-a-subtype>Postconditions cannot be weakened in a subtype</h2><p>Postconditions are usually expectations related to the method return value.</p><p>Again, this the more generic rule similar to the covariance rule (method can return Cat instead of Animal), the postcondition is strengthened.</p><p>An example of postcondition rule violation:</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-python data-lang=python><span style=display:flex><span style=color:#66d9ef>class <span style=color:#a6e22e>The24Hours</span> </span></span><span style=display:flex> number setHour(number hour) </span><span style=display:flex> <span style=color:#f92672>… </span></span><span style=display:flex> <span style=color:#66d9ef>assert (this<span style=color:#f92672>.</span>hour <span style=color:#f92672>is</span> integer) </span></span><span style=display:flex> <span style=color:#66d9ef>return this<span style=color:#f92672>.</span>hour </span></span><span style=display:flex> </span><span style=display:flex><span style=color:#66d9ef>class <span style=color:#a6e22e>TheTime</span> extends The24Hours </span></span><span style=display:flex> number setHour(number hour, number hourFraction) </span><span style=display:flex> this<span style=color:#f92672>.hour <span style=color:#f92672>=</span> hour <span style=color:#f92672>+</span> hourFraction <span style=color:#f92672>/</span> <span style=color:#ae81ff>100</span> </span></span><span style=display:flex> <span style=color:#75715e># the postcondition is weaker (float is a wider area than integer) </span></span><span style=display:flex> <span style=color:#66d9ef>assert (this<span style=color:#f92672>.</span>hour <span style=color:#f92672>is</span> float) </span></span><span style=display:flex> <span style=color:#66d9ef>return this<span style=color:#f92672>.</span>hour </span></span><span style=display:flex> </span><span style=display:flex>function doAction(The24Hours hours) </span><span style=display:flex> int result <span style=color:#f92672>= hours<span style=color:#f92672>-></span>setHour(<span style=color:#ae81ff>3</span>); <span style=color:#75715e># OK for The24Hours</span> </span></span><span style=display:flex> <span style=color:#75715e># Problem for TheTime, it returns float </span></span></code></pre></div><p>So again, due to LSP violation, we can not use the child class instead of the parent.</p><h2 id=invariants-of-the-parent-type-must-be-preserved-in-a-subtype>Invariants of the parent type must be preserved in a subtype</h2><p>Invariant is something that is not changed during the method execution. It can be the whole or part of the object internal state:</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-python data-lang=python><span style=display:flex><span style=color:#66d9ef>class <span style=color:#a6e22e>The24Hours</span> </span></span><span style=display:flex> <span style=color:#75715e># invariant: this.hour is not changed </span></span><span style=display:flex> number getHour() </span><span style=display:flex> <span style=color:#66d9ef>return this<span style=color:#f92672>.</span>hour </span></span><span style=display:flex> </span><span style=display:flex><span style=color:#66d9ef>class <span style=color:#a6e22e>TheCounter</span> extends The24Hours </span></span><span style=display:flex> <span style=color:#75715e># invariant violation: this.hour is changed </span></span><span style=display:flex> number getHour() </span><span style=display:flex> result <span style=color:#f92672>= this<span style=color:#f92672>.</span>hour </span></span><span style=display:flex> this<span style=color:#f92672>.hour <span style=color:#f92672>+=</span> <span style=color:#ae81ff>1</span> </span></span><span style=display:flex> <span style=color:#66d9ef>return result </span></span><span style=display:flex> </span><span style=display:flex>function doAction(The24Hours hours) </span><span style=display:flex> <span style=color:#66d9ef>if (hours<span style=color:#f92672>-></span>getHour() <span style=color:#f92672><=</span> <span style=color:#ae81ff>12</span>) </span></span><span style=display:flex> <span style=color:#75715e># OK for The24Hours </span></span><span style=display:flex> <span style=color:#75715e># Problem for TheTime, now getHour() can return value > 12 </span></span><span style=display:flex> print <span style=color:#e6db74>'First half of the day', hours<span style=color:#f92672>-></span>getHour() </span></span></code></pre></div><h2 id=history-constraint-the-history-rule>History constraint (the “history rule”)</h2><p>The subtypes should not introduce new methods that will allow modifying the object state in a way that is not possible for the parent class.</p><p>The internal object state should be modifiable only through their methods (encapsulation) and the client code can have some expectations as of the possible ways to modify the internal state.</p><p>For example:</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-python data-lang=python><span style=display:flex><span style=color:#66d9ef>class <span style=color:#a6e22e>Time</span> </span></span><span style=display:flex> <span style=color:#75715e># it is immutable, once time is set, there is no way to change it </span></span><span style=display:flex> constructor(int hour, int minute) </span><span style=display:flex> getTime() </span><span style=display:flex> </span><span style=display:flex><span style=color:#66d9ef>class <span style=color:#a6e22e>FlexibleTime</span> extends Time </span></span><span style=display:flex> <span style=color:#75715e># violates the "history" rule </span></span><span style=display:flex> <span style=color:#75715e># it allows changing the object state </span></span><span style=display:flex> <span style=color:#75715e># but the clients who use Time can be broken due to this </span></span><span style=display:flex> setTime(int hour, int minute) </span><span style=display:flex> </span><span style=display:flex>doAction(Time time) </span><span style=display:flex> print <span style=color:#e6db74>'Now it is: ', time<span style=color:#f92672>-></span>getTime() </span></span><span style=display:flex> doOtherAction(time) </span><span style=display:flex> <span style=color:#75715e># OK for Time, it can not be changed, so value is the same </span></span><span style=display:flex> <span style=color:#75715e># Problem for FlexibleTime, the doOtherAction could change it </span></span><span style=display:flex> print <span style=color:#e6db74>'Now it is still: ', time<span style=color:#f92672>-></span>getTime() </span></span></code></pre></div><h1 id=links>Links</h1><p><a href=https://en.wikipedia.org/wiki/Liskov_substitution_principle>Wikipedia:Liskov substitution principle</a></p><p><a href=http://www.engr.mun.ca/~theo/Courses/sd/5895-downloads/sd-principles-3.ppt.pdf>Agile Design Principles: The Liskov Substitution Principle</a></p><p><a href=https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)#Inheritance_in_object-oriented_languages>Wikipedia: Covariance and contravariance</a></p><a href=https://stackexchange.com/users/261528>profile for Boris Serebrov on Stack Exchange, a network of free, community-driven Q&A sites</a></div><div class=popup><div class=close>close</div><div class=download>Download ()</div><div class=popup-content></div></div><footer class=entry-footer><div class='container sep-before'><div class=tags><span class=screen-reader-text>Tags: </span><a class=tag href=/tags/oop/>oop</a></div></div></footer></article><nav class=entry-nav><div class=container><div class='prev-entry sep-before'><a href=/html/2015-09-22-aws-postgresql-max-connections.html> <span class=screen-reader-text>Previous post: </span>AWS PostgreSQL RDS - remaining connection slots are reserved error</a></div><div class='next-entry sep-before'><a href=/html/2016-06-10-scaleway-docker-deployment.html><span class=screen-reader-text>Next post: </span>Setup Automatic Deployment, Updates and Backups of Multiple Web Applications with Docker on the Scaleway Server</a></div></div></nav><section id=comments class=comments><div class='container sep-before'><div class=comments-area><div id=disqus_thread></div></div></div></section></main><footer id=footer class=footer><div class='container sep-before'><section class='widget widget-social_menu sep-after'><nav aria-label='Social Menu'><ul><li><a href=https://github.com/serebrov target=_blank rel=noopener><span class=screen-reader-text>Open Github account in new tab</span></a></li><li><a href=mailto:serebrov@gmail.com target=_blank rel=noopener><span class=screen-reader-text>Contact via Email</span></a></li></ul></nav></section><div class=copyright><p>© 2020-2024 Boris Serebrov</p></div></div></footer></div></div><script src=https://serebrov.github.io/assets/js/main.67d669ac.js></script><script src=/js/jquery.min.js></script><script src=/js/custom.js></script></body></html>