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




