Liskov Substitution Principle (LSP): SOLID Design for Flexible Code

liskov substitution principle lsp banner overcoded

The Liskov Substitution Principle (LSP) is one of five SOLID object-oriented design principles. It states that a superclass object should be replaceable with a subclass object without breaking the functionality of the software. It is a type of behavioral subtyping defined by semantic, rather than syntactic, design consideration.

The LSP is used to help limit code smell (a.k.a. design smell) in object-oriented programming (OOP). It is a semantic principle that describes ideal behavioral considerations for object-oriented feature designs. The LSP makes heavy use of inheritance and polymorphism and commonly leverages language features such as interfaces when multiple inheritances is not supported.

Introduction

The Liskov Substitution Principle was first introduced by Barbar Liskov informally in a keynote address in 1987 (2) and formerly in her and Jeannette Wing’s 1994 paper A Behavioral Notion of Subtyping (1). This paper describes the LSP as follows:

The relationship [of how subtypes and sypertypes] should ensure that any property proved about supertype objects also holds for its subtype objects.

The Liskov Substitution Principle is one of five core pillars of the SOLID principles of object-oriented design outlined in Robert C. Martin’s 2003 book Agile Software Development, Principles, Patterns, and Practices (3). These principles outline high-level strategies to develop more flexible, maintainable, and extensible software.

Check Out: Complete List of Computer Science, Engineering, and Programming Books

The Liskov Substitution Principle is supported by object-oriented design abstraction concepts of inheritance and polymorphism. These are often realized as the subclassing of superclass objects using abstract data types like interfaces that define common behavior.

LSP Example Code

To exemplify the LSP let us consider a hypothetical Stock trading application. This application needs to facilitate Transactions like Buy and Sell. In addition, the application needs to support multiple transaction types for different security types such as bonds, stocks, and options. We can approach this with the Liskov Substitution Principle in mind by inheriting from a base Transaction class as such:

/**
* The base Transaction class that defines a buy and sell feature.
*/
class Transaction{
    
    public void buy(String stock, int quantity, float price){
        // implement buy logic here
    };
    public void sell(String stock, int quantity, float price){
        // implement sell logic here
    }; 
}


/**
* A subclass implementation of the Transaction class that 
* defines Stock-specific buy and sell action logic.
*/
class StockTransaction extends Transaction{
    
    @Override
    public void buy(String stock, int quantity, float price){
        // implement Stock-specific buy logic here
    }

    @Override
    public void sell(String stock, int quantity, float price){
        // implement Stock-specific sell logic here
    }
}

In this example, the Transaction class is subclassed by StockTransaction where the base buy() and sell() methods are overridden to provide the desired functionality. This approach, while basic and lackluster in many regards, conforms to the Liskov Substitution Principle by assuring that substituting a subclass instance of StockTransaction for a superclass instance of Transaction will not break core functionality. More concretely, the substituted subclass instance will still provide buy and sell functionality that could be called in the same manner.

Final Thoughts

The Liskov Substitution Principle has been a core tenant of good software design for decades. Its inclusion by Martin in his 2003 Agile Software Development, Principles, Patterns, and Practices solidified it (pardon the pun) as a standard principle within the field of software design.

As with other principles of SOLID design, the LSP can become an antipattern—meaning sometimes the added complexity is not worth the tradeoff. Barring certain such cases, the LSP can help ensure a more flexible, maintainable, and extensible design. Learning the ways of the LSP will, at the very least, help one become a better software designer by understanding when not to apply it!

References

  1. Liskov, Barbara H., and Jeannette M. Wing. “A Behavioral Notion of Subtyping.” ACM Transactions on Programming Languages and Systems, vol. 16, no. 6, 1994, pp. 1811–41. doi:10.1145/197320.197383.
  2. Liskov, B. “Data Abstraction and Hierarchy.” (1987).
  3. Martin, Robert C. Agile Software Development, Principles, Patterns, and Practices. 1st ed., Pearson, 2002.
Zαck West
Full-Stack Software Engineer with 10+ years of experience. Expertise in developing distributed systems, implementing object-oriented models with a focus on semantic clarity, driving development with TDD, enhancing interfaces through thoughtful visual design, and developing deep learning agents.