avatarJavier Lopez

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

2788

Abstract

in memory (a fake one for testing) and the other one running against a database (the real one).</p><figure id="5e7e"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*wlSKBOdYk6Sl_xgMT1Rsyw.png"><figcaption></figcaption></figure><figure id="dd6f"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*AKK6u3BUtxAkKvd9NErnqA.png"><figcaption></figcaption></figure><p id="694b">What is the semantic relation between these three types and our client?, there are tons of things, but let’s focus just on the simplest one:</p><ul><li>If the client save a Booking with a bookingId then the client will be able to retrieve that booking with the same bookingId.</li></ul><p id="b6e9">So let’s create a contract test to check that both implementations fulfill that requirement imposed by the code that uses the parent type (the client):</p><figure id="5f84"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*oEBnXoJb1DhLKCZHdJbJng.png"><figcaption></figcaption></figure><figure id="076f"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*f6u22ZaQ2lp4kDBYRjCsPA.png"><figcaption></figcaption></figure><figure id="4567"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*esK9ZmK9QT8SVyxFfS8Vzg.png"><figcaption></figcaption></figure><p id="8a67"><b>InMemoryLiskovTest</b> and <b>DBLiskovTest</b> run the same contract to check that they both honor the intentions of the client that use the code based in the parent type.</p><p id="0d32">To allow introducing one or the other implementation in my code, I need to use the parent type, not any of the subtypes. So as my <b>InMemoryBookingRepository</b> need to be introduced in my production code when testing I need to allow my production code to accept <b>InMemoryBookingRepository</b> but also <b>DBBookingRepository</b> when running in production, so it needs to depend on <b>Repository</b> interface (another SOLID principle, Dependency Inversion, depend on abstractions not in concretions).</p><p id="a550">Imagine that the <b>DBBookingRepository</b> throws an exception when the client tries to save a booking in the database that already exist, the <b>InMemoryBookingRepository</b> doesn’t do that and your client doesn’t expect that. Then you have a bug in your system because you are not honoring Liskov. If your code honor Liskov then you can inject in your code any of these instances and nothing will break.</p><p id="708b">Imagine that for any reason the client (the production code) needs to check if the instance it has is an object of the type <b>DBBookingRepository</b> to do a special thing, then I’m breaking Liskov principle. If you have to do this in any part of your code, you are not honoring Liskov.</p><div id="5d6e"><pre><span class="hljs-built_in">if<

Options

/span>(repository instanceof DBBookingRepository) { <span class="hljs-comment">// do some DB specific things</span> } </pre></div><p id="9316">You can think of other contracts depending on the usage of your code by your client (production code), all of them need to be true (semantic relationship). Also, you can figure out that I haven’t used inheritance (yes using interfaces is not inheritance), I just created types and subtypes, so this is valid for any language not just for OOP languages.</p><p id="d0b3">With these eyes, perhaps it is easier to understand what means the rules under LSP.</p><blockquote id="ffeb"><p>The subtype must meet a number of behavioural conditions. These are detailed in a terminology resembling that of <a href="https://en.wikipedia.org/wiki/Design_by_contract">design by contract</a> methodology, leading to some restrictions on how contracts can interact with <a href="https://en.wikipedia.org/wiki/Inheritance_(object-oriented_programming)">inheritance</a>:</p></blockquote><blockquote id="9628"><p><a href="https://en.wikipedia.org/wiki/Precondition">Preconditions</a> cannot be strengthened in the subtype.</p></blockquote><blockquote id="7d3b"><p><a href="https://en.wikipedia.org/wiki/Postcondition">Postconditions</a> cannot be weakened in the subtype.</p></blockquote><blockquote id="aef5"><p><a href="https://en.wikipedia.org/wiki/Invariant_(computer_science)">Invariant</a> cannot be weakened in the subtype.</p></blockquote><blockquote id="3d42"><p>History constraint (the “history rule”). Objects are regarded as being modifiable only through their methods (<a href="https://en.wikipedia.org/wiki/Encapsulation_(computer_science)">encapsulation</a>). Because subtypes may introduce methods that are not present in the supertype, the introduction of these methods may allow state changes in the subtype that are not permissible in the supertype. The history constraint prohibits this. It was the novel element introduced by Liskov and Wing. A violation of this constraint can be exemplified by defining a <i>mutable point</i> as a subtype of an <i>immutable point</i>. This is a violation of the history constraint, because in the history of the <i>immutable point</i>, the state is always the same after creation, so it cannot include the history of a <i>mutable point</i> in general. Fields added to the subtype may however be safely modified because they are not observable through the supertype methods. Thus, one can define a <i>circle with immutable center and mutable radius</i> as a subtype of an <i>immutable point</i> without violating the history constraint. <a href="https://en.wikipedia.org/wiki/Liskov_substitution_principle">https://en.wikipedia.org/wiki/Liskov_substitution_principle</a></p></blockquote></article></body>

Liskov Substitution Principle (basics)

The LSP is one of the lesser known SOLID principles, but in my opinion, it is crucial to create good types and subtypes.

Barbara Liskov

The Liskov substitution principle (LSP) is a particular definition of a subtyping relation, called strong behavioral subtyping, that was initially introduced by Barbara Liskov in a 1987 conference keynote address titled Data abstraction and hierarchy. It is based on the concept of “substitutability” — a principle in object-oriented programming stating that an object (such as a class) may be replaced by a sub-object (such as a class that extends the first class) without breaking the program. It is a semantic rather than merely syntactic relation, because it intends to guarantee semantic interoperability of types in a hierarchy, object types in particular. https://en.wikipedia.org/wiki/Liskov_substitution_principle

The above is the definition of the principle, basically what LSP states is that there is a semantic relation between three parts of your code when you are using types and subtypes:

  • The client, the part of your code that is using one instance of your hierarchy of types.
  • The parent type.
  • The subtypes.

The important word here is semantic, because what is saying that word is that the client of your types is going to assume that your parent type will have some properties, apart from the syntax.

That assumption creates a contract between the client and the parent type, but also that contract needs to be valid for all the subtypes created based on that parent type. If the contract is broken by any subtype, then that subtype should not be part of that hierarchy typing structure.

Let’s use an example, I will use contract testing to show what means all of this. Let’s imagine that we have a booking system and at some point of our system we create a BookingRepository interface (the parent type):

And because of my tests, we are going to create two implementations of this repository, one in memory (a fake one for testing) and the other one running against a database (the real one).

What is the semantic relation between these three types and our client?, there are tons of things, but let’s focus just on the simplest one:

  • If the client save a Booking with a bookingId then the client will be able to retrieve that booking with the same bookingId.

So let’s create a contract test to check that both implementations fulfill that requirement imposed by the code that uses the parent type (the client):

InMemoryLiskovTest and DBLiskovTest run the same contract to check that they both honor the intentions of the client that use the code based in the parent type.

To allow introducing one or the other implementation in my code, I need to use the parent type, not any of the subtypes. So as my InMemoryBookingRepository need to be introduced in my production code when testing I need to allow my production code to accept InMemoryBookingRepository but also DBBookingRepository when running in production, so it needs to depend on Repository interface (another SOLID principle, Dependency Inversion, depend on abstractions not in concretions).

Imagine that the DBBookingRepository throws an exception when the client tries to save a booking in the database that already exist, the InMemoryBookingRepository doesn’t do that and your client doesn’t expect that. Then you have a bug in your system because you are not honoring Liskov. If your code honor Liskov then you can inject in your code any of these instances and nothing will break.

Imagine that for any reason the client (the production code) needs to check if the instance it has is an object of the type DBBookingRepository to do a special thing, then I’m breaking Liskov principle. If you have to do this in any part of your code, you are not honoring Liskov.

if(repository instanceof DBBookingRepository) {
      // do some DB specific things
} 

You can think of other contracts depending on the usage of your code by your client (production code), all of them need to be true (semantic relationship). Also, you can figure out that I haven’t used inheritance (yes using interfaces is not inheritance), I just created types and subtypes, so this is valid for any language not just for OOP languages.

With these eyes, perhaps it is easier to understand what means the rules under LSP.

The subtype must meet a number of behavioural conditions. These are detailed in a terminology resembling that of design by contract methodology, leading to some restrictions on how contracts can interact with inheritance:

Preconditions cannot be strengthened in the subtype.

Postconditions cannot be weakened in the subtype.

Invariant cannot be weakened in the subtype.

History constraint (the “history rule”). Objects are regarded as being modifiable only through their methods (encapsulation). Because subtypes may introduce methods that are not present in the supertype, the introduction of these methods may allow state changes in the subtype that are not permissible in the supertype. The history constraint prohibits this. It was the novel element introduced by Liskov and Wing. A violation of this constraint can be exemplified by defining a mutable point as a subtype of an immutable point. This is a violation of the history constraint, because in the history of the immutable point, the state is always the same after creation, so it cannot include the history of a mutable point in general. Fields added to the subtype may however be safely modified because they are not observable through the supertype methods. Thus, one can define a circle with immutable center and mutable radius as a subtype of an immutable point without violating the history constraint. https://en.wikipedia.org/wiki/Liskov_substitution_principle

Solid
Software Engineering
Recommended from ReadMedium