liveBook · Manning

Liskov Substitution Principle

As explained in Software Design for Python Programmers

The Liskov Substitution Principle is a design principle requiring objects of subclasses to be substitutable for objects of their superclass without invalidating program correctness.

The Liskov Substitution Principle requires a subclass object to replace its superclass object without causing logic or runtime errors, while still producing meaningful results. section 7.2

CircularBuffer as a list

A CircularBuffer subclassing list inherits operations such as indexing and pop(). Client code can mutate the buffer through those operations, eventually causing an IndexError. Making CircularBuffer a standalone class and storing a private list prevents those inappropriate substitutions and operations. section 7.2

Listing 7.12 (Program 7.6 CircularBuffer-2): buffer.py (poorly designed)
class CircularBuffer(list):    #1
    def __init__(self, buffer_size):
        self._buffer_size = buffer_size
        self._head = 0
        self._tail = 0
        self._values_count = 0

        for _ in range(buffer_size):
            self.append(None)    #2

    @property
    def values_count(self): return self._values_count

    def add(self, value): 
        self[self._tail] = value    #2
        self._tail = (self._tail + 1)%self._buffer_size
        self._values_count += 1

    def remove(self):
        value = self[self._head]    #2
        self._head = (self._head + 1)%self._buffer_size
        self._values_count -= 1

        return value

    def __str__(self):    #3
        i = self._head
        c = self._values_count

        result = 'Contents:'

        while c > 0:
            result += f' {self[i]}'    #2
            i = (i + 1)%self._buffer_size
            c -= 1

        return result

Inspect the poorly designed CircularBuffer implementation to see how inherited list behavior exposes operations that conflict with the buffer abstraction. section 7.2

How Software Design for Python Programmers covers it

How deep it goes
Explains substitution through a failing CircularBuffer example and a refactoring, without developing formal contract rules in detail.
Examples
Uses a Python CircularBuffer subclass of list, invalid indexing and pop operations, and a private-list refactoring.
What it assumes
Assumes basic object-oriented inheritance and teaches how subclass substitution exposes improper class relationships.
What you can do after
Diagnose subclass designs that permit invalid inherited operations and refactor them toward a suitable class relationship.
How it connects
Connects inheritance, method overriding, composition, and programming by contract.

Also explained in

Compare how 5 books explain Liskov Substitution Principle →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Software Design for Python Programmers. The section links point to the parts of the book it draws on.