The Liskov Substitution Principle

The Liskov Substitution Principle (LSP) is named after computer scientist Barbara Liskov, inventor of the CLU language. This language emphasizes the concept of a cluster containing a description of the representation of an object and the implementations of all the operations on that object. For more information on this early object-oriented programming language, see http://www.pmg.lcs.mit.edu/CLU.html

The LSP is often summarized as subtypes must be substitutable for their base types. This advice tends to direct us toward creating polymorphic type hierarchies. If, for example, we wish to add features to the Hand class, we should make sure any subclass of Hand can be used as a direct replacement for Hand

In Python, a subclass that extends a superclass by adding new methods is an ideal design. This subclass extension demonstrates the LSP directly.

When a subclass method has a different implementation but the same type hint signature as a superclass, this also demonstrates elegant Liskov substitutability. The examples shown previously include DominoBoneYard2 and DominoBoneYard3. Both of these classes have the same methods with the same type hints and parameters. The implementations are different. The subclass can substitute for the parent class.

In some cases, we'd like to have a subclass that uses additional parameters or has a slightly different type signature. A design where a subclass method doesn't match the superclass method is often less than ideal, and an alternative design should be considered. In many cases, this should be done by wrapping the superclass instead of extending it.

Wrapping a class to add features is a way to create a new entity without creating Liskov Substitution problems. Here is an example:

class FancyDealer4:

def __init__(self):
self.boneyard = DominoBoneYard3()

def hand_iter(self, players: int, tiles: int) -> Iterator[Hand3]:
if players * tiles > len(self.boneyard._dominoes):
raise ValueError(f"Can't deal players={players} tiles={tiles}")
for p in range(players):
hand = Hand3(self.boneyard._dominoes[:tiles])
self.boneyard._dominoes = self.boneyard._dominoes[tiles:]
yield hand

The FancyDealer4 class definition is not a subclass of the previous DominoBoneYard2 or DominoBoneYard3 classes. This wrapper defines a distinct signature for the hand_iter() method: this is an additional parameter, and there are no default values. Each instance of FancyDealer4 wraps a DominoBoneYard3 instance; this object is used to manage the details of the available tiles. 

Wrapping a class makes an explicit claim that the LSP is not a feature of the class design. The choice between writing a wrapper or creating a subclass is often informed by the LSP.

Python's use of default values and keyword parameters provides a tremendous amount of flexibility. In many cases, we can consider rewriting a superclass to provide suitable defaults. This is often a way to avoid creating more subclasses or more wrapper classes. In some languages, the compiler rules for inheritance require considerable cleverness to get to a class hierarchy where a subclass can be used in place of the superclass. In Python, cleverness is rarely required; instead, we can often add optional parameters.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset