What is Liskov’s Substitution Principle?
In this textual content, we’ll uncover the Liskov’s substitution principle, considered one of many SOLID guidelines and learn how to implement it in a Pythonic strategy. The SOLID guidelines entail a sequence of superb practices to appreciate better-quality software program program. In case a number of of you aren’t aware of what SOLID stands for, proper right here it is:
- S: Single accountability principle
- O: Open/closed principle
- L: Liskov’s substitution principle
- I: Interface segregation principle
- D: Dependency inversion principle
The purpose of this textual content is to implement right class hierarchies in object-oriented design, by complying with Liskov’s substitution principle.
Liskov’s substitution principle
Liskov’s substitution principle (LSP) states that there is a sequence of properties that an object kind ought to keep to guard the reliability of its design.
The important thought behind LSP is that, for any class, a consumer should be able to use any of its subtypes indistinguishably, with out even noticing, and resulting from this truth with out compromising the anticipated conduct at runtime. That implies that purchasers are totally isolated and unaware of changes throughout the class hierarchy.
More formally, this is the distinctive definition (LISKOV 01) of LSP: if S is a subtype of T, then objects of kind T may be modified by objects of kind S, with out breaking this technique.
This is perhaps understood with the help of a generic diagram comparable to the subsequent one. Imagine that there is some shopper class that requires (comprises) objects of 1 different kind. Generally speaking, we’ll want this shopper to work along with objects of some kind, significantly, it may work by the use of an interface.
Now, this kind may as correctly be solely a generic interface definition, an abstract class or an interface, not a class with the conduct itself. There may be quite a few subclasses extending this kind (described in Figure 1 with the title Subtype, as a lot as N). The thought behind this principle is that if the hierarchy is precisely carried out, the patron class has to have the power to work with circumstances of any of the subclasses with out even noticing. These objects should be interchangeable, as Figure 1 displays:
Figure 1: A generic subtypes hierarchy
This is related to totally different design guidelines now we have now already visited, like designing for interfaces. An excellent class ought to define a clear and concise interface, and as long as subclasses honor that interface, this technique will keep applicable.
As a consequence of this, the principle moreover pertains to the ideas behind designing by contract. There is a contract between a given kind and a consumer. By following the foundations of LSP, the design will be sure that subclasses respect the contracts as they’re outlined by father or mom classes.
Detecting LSP factors with devices
There are some eventualities so notoriously incorrect with respect to the LSP that they’re usually merely acknowledged by the devices comparable to mypy and pylint.
Using mypy to detect incorrect approach signatures
By using kind annotations, all via our code, and configuring mypy, we’ll shortly detect some major errors early, and take a look at major compliance with LSP freed from cost.
If considered one of many subclasses of the Event class had been to override a approach in an incompatible vogue, mypy would uncover this by inspecting the annotations:
class Event:
…
def meets_condition(self, event_data: dict) -> bool:
return False
class LoginEvent(Event):
def meets_condition(self, event_data: guidelines) -> bool:
return bool(event_data)
When we run mypy on this file, we’ll get an error message saying the subsequent:
error: Argument 1 of “meets_condition” incompatible with supertype “Event”
The violation to LSP is clear—given that derived class is using a kind for the event_data parameter that is completely totally different from the one outlined on the underside class, we won’t anticipate them to work equally. Remember that, in response to this principle, any caller of this hierarchy has to have the power to work with Event or LoginEvent transparently, with out noticing any distinction. Interchanging objects of these two varieties should not make the equipment fail. Failure to take motion would break the polymorphism on the hierarchy.
The comparable error would have occurred if the return kind was modified for one factor aside from a Boolean value. The rationale is that purchasers of this code predict a Boolean value to work with. If considered one of many derived classes changes this return kind, it is perhaps breaking the contract, and as soon as extra, we won’t anticipate this technique to proceed working often.
A quick remember about varieties that are not the equivalent nonetheless share a typical interface: regardless that this is solely a straightforward occasion to exhibit the error, it is nonetheless true that every dictionaries and lists have one factor in widespread; they’re every iterables. This implies that in some circumstances, it might be reliable to have a approach that expects a dictionary and one different one anticipating to acquire a listing, as long as every cope with the parameters by the use of the iterable interface. In this case, the difficulty would not lie throughout the logic itself (LSP may nonetheless apply), nonetheless throughout the definition of the styles of the signature, which should be taught neither guidelines nor dict, nonetheless a union of every. Regardless of the case, one factor have to be modified, whether or not or not it is the code of the tactic, the entire design, or just the sort annotations, nonetheless in no case should we silence the warning and ignore the error given by mypy.
Note: Do not ignore errors comparable to this by using # kind: ignore or one factor comparable. Refactor or change the code to unravel the precise draw back. The devices are reporting an exact design flaw for a reliable objective.
This principle moreover is wise from an object-oriented design perspective. Remember that subclassing should create additional explicit varieties, nonetheless each subclass ought to be what the daddy or mom class declares. With the occasion from the sooner half, the system monitor wishes to have the power to work with any of the event varieties interchangeably. But each of these event varieties is an event (a LoginEvent ought to be an Event, and so ought to the rest of the subclasses). If any of these objects break the hierarchy by not implementing a message from the underside Event class, implementing one different public approach not declared on this one, or altering the signature of the methods, then the identify_event approach may not work.
Detecting incompatible signatures with pylint
Another strong violation of LSP is when, instead of varied the styles of the parameters on the hierarchy, the signatures of the methods differ totally. This may appear to be pretty a blunder, nonetheless detecting it will not on a regular basis be very easy to remember; Python is interpreted, so there is no compiler to detect most of those errors early on, and resulting from this truth they will not be caught until runtime. Luckily, now we have now static code analyzers comparable to mypy and pylint to catch errors comparable to this one early on.
While mypy could even catch most of those errors, it is a superb suggestion to moreover run pylint to appreciate additional notion.
In the presence of a class that breaks the compatibility outlined by the hierarchy (as an illustration, by altering the signature of the tactic, together with a further parameter, and so forth) comparable to the subsequent:
# lsp_1.py
class LogoutEvent(Event):
def meets_condition(self, event_data: dict, override: bool) -> bool:
if override:
return True
…
pylint will detect it, printing an informative error:
Parameters differ from overridden ‘meets_condition’ approach (arguments-differ)
Once as soon as extra, like throughout the earlier case, do not suppress these errors. Pay consideration to the warnings and errors the devices give and adapt the code accordingly.
Remarks on the LSP
The LSP is fundamental to good object-oriented software program program design on account of it emphasizes thought-about considered one of its core traits—polymorphism. It is about creating applicable hierarchies so that classes derived from a base one are polymorphic alongside the daddy or mom one, with respect to the methods on their interface.
It is moreover attention-grabbing to notice how this principle pertains to the sooner one—if we attempt to enhance a class with a model new one which is incompatible, it may fail, the contract with the patron will most likely be broken, and in consequence such an extension is not going to be attainable (or, to make it attainable, we should break the alternative end of the principle and modify code throughout the shopper that should be closed for modification, which is totally undesirable and unacceptable).
Carefully obsessed with new classes in the way in which wherein that LSP suggests helps us to extend the hierarchy precisely. We could then say that LSP contributes to the OCP.
The SOLID guidelines are key pointers for good object-oriented software program program design. Learn additional about SOLID guidelines and clear coding with the e-book Clean Code in Python, Second Edition by Mariano Anaya.