liveBook · Manning

Liskov Substitution Principle

As explained in Functional Design and Architecture: Examples in Haskell

A design principle requiring objects substituted for an abstraction to preserve the behavioral expectations of clients using that abstraction.

The Liskov Substitution Principle (LSP) requires every implementation of an abstraction to preserve the behavioral expectations expressed by its interface. Client code should use an ILampSwitcher without changing when the implementation changes. section 1.4.3

trait ILampSwitcher {                              #1
  def switch(onOff: bool)
}
  
class DaylightLamp extends ILampSwitcher           #2
class TableLamp    extends ILampSwitcher           #3
  
def turnAllOff(lamps: List[ILampSwitcher]) {       #4
  lamps.foreach(_.switch(false))
}

A client can turn every ILampSwitcher off through the shared switch operation, without knowing whether it receives a DaylightLamp or TableLamp. section 1.4.3

A shared interface is not enough by itself. TableLamp violates the contract by throwing an unexpected exception from switch, so substituting it breaks otherwise valid client code. The implementation must honor the interface’s supposed correct behavior. section 1.4.3

Listing 1.6 Concrete lamp code
class DaylightLamp (n: String, v: Int, onOff: Boolean)  #1
  extends ILampSwitcher {
    var isOn: Boolean = onOff
    var value: Int    = v
    val name: String  = n
    def switch(onOff: Boolean) = { 
      isOn = onOff
    }
}
  
class TableLamp (n: String, onOff: Boolean)             #2
extends ILampSwitcher {
  var isOn: Boolean = onOff
  val name: String  = n
  def switch(onOff: Boolean) = {  
    isOn = onOff
    // Debug: will remove it later!
    throw new Exception("switched")                     #3
  }
}

The concrete lamp code exposes the flaw: TableLamp changes state and then throws, while DaylightLamp completes the same operation normally. section 1.4.3

How Functional Design and Architecture: Examples in Haskell covers it

How deep it goes
Explains substitution through an interface and a concrete failure, stopping short of formal contract rules.
Examples
Uses ILampSwitcher, DaylightLamp, TableLamp, and a turnAllOff client to demonstrate valid and broken substitution.
What it assumes
Assumes interfaces, implementations, and object-oriented polymorphism while relating the principle to broader design practices.
What you can do after
Diagnose substitution violations by checking whether an implementation preserves the interface behavior clients rely on.
How it connects
Connects LSP with SOLID principles, interfaces, implementations, contracts, and polymorphism.

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 Functional Design and Architecture: Examples in Haskell. The section links point to the parts of the book it draws on.