I have heard that the Liskov Substitution Principle (LSP) is a fundamental principle of object oriented design. What is it and what are some examples of its use?
I have heard that the Liskov Substitution Principle (LSP) is a fundamental principle of object oriented design. What is it and what are some examples of its use?
A great example illustrating LSP (given by Uncle Bob in a podcast I heard recently) was how sometimes something that sounds right in natural language doesn't quite work in code.
In mathematics, a Square is a Rectangle. Indeed it is a specialization of a rectangle. The "is a" makes you want to model this with inheritance. However if in code you made Square derive from Rectangle, then a Square should be usable anywhere you expect a Rectangle. This makes for some strange behavior.
Imagine you had SetWidth and SetHeight methods on your Rectangle base class; this seems perfectly logical. However if your Rectangle reference pointed to a Square, then SetWidth and SetHeight doesn't make sense because setting one would change the other to match it. In this case Square fails the Liskov Substitution Test with Rectangle and the abstraction of having Square inherit from Rectangle is a bad one.
Y'all should check out the other priceless SOLID Principles Explained With Motivational Posters.
The Liskov Substitution Principle (LSP, lsp) is a concept in Object Oriented Programming that states:
Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
At its heart LSP is about interfaces and contracts as well as how to decide when to extend a class vs. use another strategy such as composition to achieve your goal.
The most effective way I have seen to illustrate this point was in Head First OOA&D. They present a scenario where you are a developer on a project to build a framework for strategy games.
They present a class that represents a board that looks like this:
![]()
All of the methods take X and Y coordinates as parameters to locate the tile position in the two-dimensional array of Tiles. This will allow a game developer to manage units in the board during the course of the game.
The book goes on to change the requirements to say that the game frame work must also support 3D game boards to accommodate games that have flight. So a ThreeDBoard class is introduced that extends Board.
At first glance this seems like a good decision. Board provides both the Height and Width properties and ThreeDBoard provides the Z axis.
Where it breaks down is when you look at all the other members inherited from Board. The methods for AddUnit, GetTile, GetUnits and so on, all take both X and Y parameters in the Board class but the ThreeDBoard needs a Z parameter as well.
So you must implement those methods again with a Z parameter. The Z parameter has no context to the Board class and the inherited methods from the Board class lose their meaning. A unit of code attempting to use the ThreeDBoard class as its base class Board would be very out of luck.
Maybe we should find another approach. Instead of extending Board, ThreeDBoard should be composed of Board objects. One Board object per unit of the Z axis.
This allows us to use good object oriented principles like encapsulation and reuse and doesn’t violate LSP.
LSP concerns invariants.
The classic example is given by the following pseudo-code declaration (implementations omitted):
class Rectangle {
int getHeight()
void setHeight(int value) {
postcondition: width didn’t change
}
int getWidth()
void setWidth(int value) {
postcondition: height didn’t change
}
}
class Square extends Rectangle { }
Now we have a problem although the interface matches. The reason is that we have violated invariants stemming from the mathematical definition of squares and rectangles. The way getters and setters work, a Rectangle should satisfy the following invariant:
void invariant(Rectangle r) {
r.setHeight(200)
r.setWidth(100)
assert(r.getHeight() == 200 and r.getWidth() == 100)
}
However, this invariant (as well as the explicit postconditions) must be violated by a correct implementation of Square, therefore it is not a valid substitute of Rectangle.
Robert Martin has an excellent paper on the Liskov Substitution Principle. It discusses subtle and not-so-subtle ways in which the principle may be violated.
Some relevant parts of the paper (note that the second example is heavily condensed):
A Simple Example of a Violation of LSP
One of the most glaring violations of this principle is the use of C++ Run-Time Type Information (RTTI) to select a function based upon the type of an object. i.e.:
void DrawShape(const Shape& s) { if (typeid(s) == typeid(Square)) DrawSquare(static_cast<Square&>(s)); else if (typeid(s) == typeid(Circle)) DrawCircle(static_cast<Circle&>(s)); }Clearly the
DrawShapefunction is badly formed. It must know about every possible derivative of theShapeclass, and it must be changed whenever new derivatives ofShapeare created. Indeed, many view the structure of this function as anathema to Object Oriented Design.Square and Rectangle, a More Subtle Violation.
However, there are other, far more subtle, ways of violating the LSP. Consider an application which uses the
Rectangleclass as described below:class Rectangle { public: void SetWidth(double w) {itsWidth=w;} void SetHeight(double h) {itsHeight=w;} double GetHeight() const {return itsHeight;} double GetWidth() const {return itsWidth;} private: double itsWidth; double itsHeight; };[...] Imagine that one day the users demand the ability to manipulate squares in addition to rectangles. [...]
Clearly, a square is a rectangle for all normal intents and purposes. Since the ISA relationship holds, it is logical to model the
Squareclass as being derived fromRectangle. [...]
Squarewill inherit theSetWidthandSetHeightfunctions. These functions are utterly inappropriate for aSquare, since the width and height of a square are identical. This should be a significant clue that there is a problem with the design. However, there is a way to sidestep the problem. We could overrideSetWidthandSetHeight[...]But consider the following function:
void f(Rectangle& r) { r.SetWidth(32); // calls Rectangle::SetWidth }If we pass a reference to a
Squareobject into this function, theSquareobject will be corrupted because the height won’t be changed. This is a clear violation of LSP. The function does not work for derivatives of its arguments.[...]
Long story short, let's leave rectangles rectangles and squares squares, practical example when extending a parent class, you have to either PRESERVE the exact parent API or to EXTEND IT.
Let's say you have a base ItemsRepository.
class ItemsRepository
{
/**
* @return int Returns number of deleted rows
*/
public function delete()
{
// perform a delete query
$numberOfDeletedRows = 10;
return $numberOfDeletedRows;
}
}
And a sub class extending it:
class BadlyExtendedItemsRepository extends ItemsRepository
{
/**
* @return void Was suppose to return an INT like parent, but did not, breaks LSP
*/
public function delete()
{
// perform a delete query
$numberOfDeletedRows = 10;
// we broke the behaviour of the parent class
return;
}
}
Then you could have a Client working with the Base ItemsRepository API and relying on it.
/**
* Class ItemsService is a client for public ItemsRepository "API" (the public delete method).
*
* Technically, I am able to pass into a constructor a sub-class of the ItemsRepository
* but if the sub-class won't abide the base class API, the client will get broken.
*/
class ItemsService
{
/**
* @var ItemsRepository
*/
private $itemsRepository;
/**
* @param ItemsRepository $itemsRepository
*/
public function __construct(ItemsRepository $itemsRepository)
{
$this->itemsRepository = $itemsRepository;
}
/**
* !!! Notice how this is suppose to return an int. My clients expect it based on the
* ItemsRepository API in the constructor !!!
*
* @return int
*/
public function delete()
{
return $this->itemsRepository->delete();
}
}
The LSP is broken when substituting parent class with a sub class breaks the API's contract.
class ItemsController
{
/**
* Valid delete action when using the base class.
*/
public function validDeleteAction()
{
$itemsService = new ItemsService(new ItemsRepository());
$numberOfDeletedItems = $itemsService->delete();
// $numberOfDeletedItems is an INT :)
}
/**
* Invalid delete action when using a subclass.
*/
public function brokenDeleteAction()
{
$itemsService = new ItemsService(new BadlyExtendedItemsRepository());
$numberOfDeletedItems = $itemsService->delete();
// $numberOfDeletedItems is a NULL :(
}
}
You can learn more about writing maintainable software in my course: https://www.udemy.com/enterprise-php/
Let’s illustrate in Java:
class TrasportationDevice
{
String name;
String getName() { ... }
void setName(String n) { ... }
double speed;
double getSpeed() { ... }
void setSpeed(double d) { ... }
Engine engine;
Engine getEngine() { ... }
void setEngine(Engine e) { ... }
void startEngine() { ... }
}
class Car extends TransportationDevice
{
@Override
void startEngine() { ... }
}
There is no problem here, right? A car is definitely a transportation device, and here we can see that it overrides the startEngine() method of its superclass.
Let’s add another transportation device:
class Bicycle extends TransportationDevice
{
@Override
void startEngine() /*problem!*/
}
Everything isn’t going as planned now! Yes, a bicycle is a transportation device, however, it does not have an engine and hence, the method startEngine() cannot be implemented.
These are the kinds of problems that violation of Liskov Substitution Principle leads to, and they can most usually be recognized by a method that does nothing, or even can’t be implemented.
The solution to these problems is a correct inheritance hierarchy, and in our case we would solve the problem by differentiating classes of transportation devices with and without engines. Even though a bicycle is a transportation device, it doesn’t have an engine. In this example our definition of transportation device is wrong. It should not have an engine.
We can refactor our TransportationDevice class as follows:
class TrasportationDevice
{
String name;
String getName() { ... }
void setName(String n) { ... }
double speed;
double getSpeed() { ... }
void setSpeed(double d) { ... }
}
Now we can extend TransportationDevice for non-motorized devices.
class DevicesWithoutEngines extends TransportationDevice
{
void startMoving() { ... }
}
And extend TransportationDevice for motorized devices. Here is is more appropriate to add the Engine object.
class DevicesWithEngines extends TransportationDevice
{
Engine engine;
Engine getEngine() { ... }
void setEngine(Engine e) { ... }
void startEngine() { ... }
}
Thus our Car class becomes more specialized, while adhering to the Liskov Substitution Principle.
class Car extends DevicesWithEngines
{
@Override
void startEngine() { ... }
}
And our Bicycle class is also in compliance with the Liskov Substitution Principle.
class Bicycle extends DevicesWithoutEngines
{
@Override
void startMoving() { ... }
}
The LSP is a rule about the contract of the clases: if a base class satisfies a contract, then by the LSP derived classes must also satisfy that contract.
In Pseudo-python
class Base:
def Foo(self, arg):
# *... do stuff*
class Derived(Base):
def Foo(self, arg):
# *... do stuff*
satisfies LSP if every time you call Foo on a Derived object, it gives exactly the same results as calling Foo on a Base object, as long as arg is the same.
Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
When I first read about LSP, I assumed that this was meant in a very strict sense, essentially equating it to interface implementation and type-safe casting. Which would mean that LSP is either ensured or not by the language itself. For example, in this strict sense, ThreeDBoard is certainly substitutable for Board, as far as the compiler is concerned.
After reading up more on the concept though I found that LSP is generally interpreted more broadly than that.
In short, what it means for client code to "know" that the object behind the pointer is of a derived type rather than the pointer type is not restricted to type-safety. Adherence to LSP is also testable through probing the objects actual behavior. That is, examining the impact of an object's state and method arguments on the results of the method calls, or the types of exceptions thrown from the object.
Going back to the example again, in theory the Board methods can be made to work just fine on ThreeDBoard. In practice however, it will be very difficult to prevent differences in behavior that client may not handle properly, without hobbling the functionality that ThreeDBoard is intended to add.
With this knowledge in hand, evaluating LSP adherence can be a great tool in determining when composition is the more appropriate mechanism for extending existing functionality, rather than inheritance.
The Liskov Substitution Principle
The LSP in simple terms states that objects of the same superclass should be able to be swapped with each other without breaking anything.
For example, if we have a Cat and a Dog class derived from an Animal class, any functions using the Animal class should be able to use Cat or Dog and behave normally.
This formulation of the LSP is way too strong:
If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2, then S is a subtype of T.
Which basically means that S is another, completely encapsulated implementation of the exact same thing as T. And I could be bold and decide that performance is part of the behavior of P...
So, basically, any use of late-binding violates the LSP. It's the whole point of OO to to obtain a different behavior when we substitute an object of one kind for one of another kind!
The formulation cited by wikipedia is better since the property depends on the context and does not necessarily include the whole behavior of the program.
Liskov's Substitution Principle(LSP)
All the time we design a program module and we create some class hierarchies. Then we extend some classes creating some derived classes.
We must make sure that the new derived classes just extend without replacing the functionality of old classes. Otherwise, the new classes can produce undesired effects when they are used in existing program modules.
Liskov's Substitution Principle states that if a program module is using a Base class, then the reference to the Base class can be replaced with a Derived class without affecting the functionality of the program module.
Example:
Below is the classic example for which the Liskov's Substitution Principle is violated. In the example, 2 classes are used: Rectangle and Square. Let's assume that the Rectangle object is used somewhere in the application. We extend the application and add the Square class. The square class is returned by a factory pattern, based on some conditions and we don't know the exact what type of object will be returned. But we know it's a Rectangle. We get the rectangle object, set the width to 5 and height to 10 and get the area. For a rectangle with width 5 and height 10, the area should be 50. Instead, the result will be 100
// Violation of Likov's Substitution Principle
class Rectangle {
protected int m_width;
protected int m_height;
public void setWidth(int width) {
m_width = width;
}
public void setHeight(int height) {
m_height = height;
}
public int getWidth() {
return m_width;
}
public int getHeight() {
return m_height;
}
public int getArea() {
return m_width * m_height;
}
}
class Square extends Rectangle {
public void setWidth(int width) {
m_width = width;
m_height = width;
}
public void setHeight(int height) {
m_width = height;
m_height = height;
}
}
class LspTest {
private static Rectangle getNewRectangle() {
// it can be an object returned by some factory ...
return new Square();
}
public static void main(String args[]) {
Rectangle r = LspTest.getNewRectangle();
r.setWidth(5);
r.setHeight(10);
// user knows that r it's a rectangle.
// It assumes that he's able to set the width and height as for the base
// class
System.out.println(r.getArea());
// now he's surprised to see that the area is 100 instead of 50.
}
}
Conclusion:
This principle is just an extension of the Open Close Principle and it means that we must make sure that new derived classes are extending the base classes without changing their behavior.
See also: Open Close Principle
Some similar concepts for better structure: Convention over configuration
This principle was introduced by Barbara Liskov in 1987 and extends the Open-Closed Principle by focusing on the behavior of a superclass and its subtypes.
Its importance becomes obvious when we consider the consequences of violating it. Consider an application that uses the following class.
public class Rectangle
{
private double width;
private double height;
public double Width
{
get
{
return width;
}
set
{
width = value;
}
}
public double Height
{
get
{
return height;
}
set
{
height = value;
}
}
}
Imagine that one day, the client demands the ability to manipulate squares in addition to rectangles. Since a square is a rectangle, the square class should be derived from the Rectangle class.
public class Square : Rectangle
{
}
However, by doing that we will encounter two problems:
A square does not need both height and width variables inherited from the rectangle and this could create a significant waste in memory if we have to create hundreds of thousands of square objects. The width and height setter properties inherited from the rectangle are inappropriate for a square since the width and height of a square are identical. In order to set both height and width to the same value, we can create two new properties as follows:
public class Square : Rectangle
{
public double SetWidth
{
set
{
base.Width = value;
base.Height = value;
}
}
public double SetHeight
{
set
{
base.Height = value;
base.Width = value;
}
}
}
Now, when someone will set the width of a square object, its height will change accordingly and vice-versa.
Square s = new Square();
s.SetWidth(1); // Sets width and height to 1.
s.SetHeight(2); // sets width and height to 2.
Let's move forward and consider this other function:
public void A(Rectangle r)
{
r.SetWidth(32); // calls Rectangle.SetWidth
}
If we pass a reference to a square object into this function, we would violate the LSP because the function does not work for derivatives of its arguments. The properties width and height aren't polymorphic because they aren't declared virtual in rectangle (the square object will be corrupted because the height won't be changed).
However, by declaring the setter properties to be virtual we will face another violation, the OCP. In fact, the creation of a derived class square is causing changes to the base class rectangle.
It states that if C is a subtype of E then E can be replaced with objects of type C without changing or breaking the behavior of the program. In simple words, derived classes should be substitutable for their parent classes. For example, if a Farmer’s son is Farmer then he can work in place of his father but if a Farmer’s son is a cricketer then he can’t work in place of his father.
public class Plane{
public void startEngine(){}
}
public class FighterJet extends Plane{}
public class PaperPlane extends Plane{}
In the given example FighterPlane and PaperPlane classes both extending the Plane class which contain startEngine() method. So it's clear that FighterPlane can start engine but PaperPlane can’t so it’s breaking LSP.
PaperPlane class although extending Plane class and should be substitutable in place of it but is not an eligible entity that Plane’s instance could be replaced by, because a paper plane can’t start the engine as it doesn’t have one. So the good example would be,
public class Plane{
}
public class RealPlane{
public void startEngine(){}
}
public class FighterJet extends RealPlane{}
public class PaperPlane extends Plane{}
The big picture :
Unlike the other answers, I won't start with a Liskov Substitution Principle (LSP) violation, but with a LSP compliance. I use Java but it would be almost the same in every OOP language.
Circle and ColoredCircleGeometrical examples seem pretty popular here.
class Circle {
private int radius;
public Circle(int radius) {
if (radius < 0) {
throw new RuntimeException("Radius should be >= 0");
}
this.radius = radius;
}
public int getRadius() {
return this.radius;
}
}
The radius is not allowed to be negative. Here's a suclass:
class ColoredCircle extends Circle {
private Color color; // defined elsewhere
public ColoredCircle(int radius, Color color) {
super(radius);
this.color = color;
}
public Color getColor() {
return this.color;
}
}
This subclass is a subtype of Circle, according to the LSP.
The LSP states that:
If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2, then S is a subtype of T. (Barbara Liskov, "Data Abstraction and Hierarchy", SIGPLAN Notices, 23,5 (May, 1988))
Here, for each ColoredCircle instance o1, consider the Circle instance having the same radius o2. For every program using Circle objects, if you replace o2 by o1, the behavior of any program using Circle will remain the same after the substitution. (Note that this is theoretical : you will exhaust the memory faster using ColoredCircle instances than using Circle instances, but that's not relevant here.)
How do we find the o2 depending on o1 ? We just strip the color attribute and keep the radius attribute. I call the transformation o1 -> o2 a projection from the CircleColor space on the Circle space.
Let's create another example to illustrate the violation of the LSP.
Circle and SquareImagine this subclass of the previous Circle class:
class Square extends Circle {
private int sideSize;
public Square(int sideSize) {
super(0);
this.sideSize = sideSize;
}
@Override
public int getRadius() {
return -1; // I'm a square, I don't care
}
public int getSideSize() {
return this.sideSize;
}
}
Now, look at this program :
public class Liskov {
public static void program(Circle c) {
System.out.println("The radius is "+c.getRadius());
}
We test the program with a Circle object and with a Square object.
public static void main(String [] args){
Liskov.program(new Circle(2)); // prints "The radius is 2"
Liskov.program(new Square(2)); // prints "The radius is -1"
}
}
What happened ? Intuitively, although Square is a subclass of Circle, Square is not a subtype of Circle because no regular Circle instance would ever have a radius of -1.
Formally, this is a violation of Liskov Substitution Principle.
We have a program defined in terms of Circle and there is no Circle object that can replace new Square(2) (or any Square instance by the way) in this program and leave the behavior unchanged: remember that radius of any Circle is always positive.
Now we know why a subclass is not always subtype. When a subclass is not a subtype, i.e. when there is a LSP violation, the behavior of some programs (at least one) won't always be the expected behavior. This is very frustrating and is usually interpreted as a bug.
In an ideal world, the compiler or interpreter would be able to check is a given subclass is a real subtype, but we are not in an ideal world.
If there is some static typing, you are bound by the superclass signature at compile time. Square.getRadius() can't return a String or a List.
If there is no static typing, you'll get an error at runtime if the type of one argument is wrong (unless the typing is weak) or the number of arguments is inconsistent (unless the language is very permissive).
Note about the static typing: there is a mechanism of covariance of the return type (a method of S can return a subclass of the return type of the same method of T) and contravariance of the parameters types (a method of S can accept a superclass of a parameter of the same parameter of the same method of T). That is a specific case of precondition and postcondition explained below.
There's more. Some languages (I think of Eiffel) provide a mechanism to enforce the compliance with the LSP.
Let alone the determination the projection o2 of the initial object o1, we can expect the same behavior of any program if o1 is substituted for o2 if, for any argument x and any method f:
o2.f(x) is a valid call, then o1.f(x) should also be a valid call (1).o1.f(x) should be equal to the result of o2.f(x), or at least equally valid (2).o1.f(x) should let o1 in an internal state and o2.f(x) should let o2 in an internal state so that next function calls will ensure that (1), (2) and (3) will still be valid (3).(Note that (3) is given for free if the function f is pure. That's why we like to have immutable objects.)
These conditions are about the semantics (what to expect) of the class, not only the syntax of the class. Also, these conditions are very strong. But they can be approximated by assertions in design by contract programming. These assertions are a way to ensure that the semantic of the type is upheld. Breaking the contract leads to runtime errors.
S.f accepts more than T.f) (a).S.f provides more than T.f) (b).We see that, roughly, (a) ensures (1) and (b) ensures (2), but (c) is weaker than (3). Moreover, assertions are sometimes difficult to express.
Think of a class Counter having a unique method Counter.counter() that returns the next integer. How do you write a postcondition for that ? Think of a class Random having a method Random.gaussian() that returns a float between 0.0 and 1.0 . How do you write a postcondition to check that the distribution is gaussian ? It may be possible, but the cost would be so high that we would rely on test rather than on postconditions.
Unfortunately, a subclass is not always a subtype. This can lead to an unexpected behavior -- a bug.
OOP languages provide mechanism to avoid this situation. At syntactic level first. At semantical level too, depending on the programming language: a part of the semantics can be encoded in the text of the program using assertions. But it's up to you to ensure that a subclass is a subtype.
Remember when you began to learn OOP ? "If the relation is IS-A, then use inheritance". That's true the other way: if you use inheritance, be sure that the relation is IS-A.
The LSP defines, at a higher level than assertions, what is a subtype. Assertions are a valuable tool to ensure that the LSP is upheld.
Would implementing ThreeDBoard in terms of an array of Board be that useful?
Perhaps you may want to treat slices of ThreeDBoard in various planes as a Board. In that case you may want to abstract out an interface (or abstract class) for Board to allow for multiple implementations.
In terms of external interface, you might want to factor out a Board interface for both TwoDBoard and ThreeDBoard (although none of the above methods fit).
Let's say we use a rectangle in our code
r = new Rectangle();
// ...
r.setDimensions(1,2);
r.fill(colors.red());
canvas.draw(r);
In our geometry class we learned that a square is a special type of rectangle because its width is the same length as its height. Let's make a Square class as well based on this info:
class Square extends Rectangle {
setDimensions(width, height){
assert(width == height);
super.setDimensions(width, height);
}
}
If we replace the Rectangle with Square in our first code, then it will break:
r = new Square();
// ...
r.setDimensions(1,2); // assertion width == height failed
r.fill(colors.red());
canvas.draw(r);
This is because the Square has a new precondition we did not have in the Rectangle class: width == height. According to LSP the Rectangle instances should be substitutable with Rectangle subclass instances. This is because these instances pass the type check for Rectangle instances and so they will cause unexpected errors in your code.
This was an example for the "preconditions cannot be strengthened in a subtype" part in the wiki article. So to sum up, violating LSP will probably cause errors in your code at some point.
LSP says that ''Objects should be replaceable by their subtypes''. On the other hand, this principle points to
Child classes should never break the parent class`s type definitions.
and the following example helps to have a better understanding of LSP.
Without LSP:
public interface CustomerLayout{
public void render();
}
public FreeCustomer implements CustomerLayout {
...
@Override
public void render(){
//code
}
}
public PremiumCustomer implements CustomerLayout{
...
@Override
public void render(){
if(!hasSeenAd)
return; //it isn`t rendered in this case
//code
}
}
public void renderView(CustomerLayout layout){
layout.render();
}
Fixing by LSP:
public interface CustomerLayout{
public void render();
}
public FreeCustomer implements CustomerLayout {
...
@Override
public void render(){
//code
}
}
public PremiumCustomer implements CustomerLayout{
...
@Override
public void render(){
if(!hasSeenAd)
showAd();//it has a specific behavior based on its requirement
//code
}
}
public void renderView(CustomerLayout layout){
layout.render();
}
Likov's Substitution Principle states that if a program module is using a Base class, then the reference to the Base class can be replaced with a Derived class without affecting the functionality of the program module.
Intent - Derived types must be completely substitute able for their base types.
Example - Co-variant return types in java.
Here is an excerpt from this post that clarifies things nicely:
[..] in order to comprehend some principles, it’s important to realize when it’s been violated. This is what I will do now.
What does the violation of this principle mean? It implies that an object doesn’t fulfill the contract imposed by an abstraction expressed with an interface. In other words, it means that you identified your abstractions wrong.
Consider the following example:
interface Account
{
/**
* Withdraw $money amount from this account.
*
* @param Money $money
* @return mixed
*/
public function withdraw(Money $money);
}
class DefaultAccount implements Account
{
private $balance;
public function withdraw(Money $money)
{
if (!$this->enoughMoney($money)) {
return;
}
$this->balance->subtract($money);
}
}
Is this a violation of LSP? Yes. This is because the account’s contract tells us that an account would be withdrawn, but this is not always the case. So, what should I do in order to fix it? I just modify the contract:
interface Account
{
/**
* Withdraw $money amount from this account if its balance is enough.
* Otherwise do nothing.
*
* @param Money $money
* @return mixed
*/
public function withdraw(Money $money);
}
Voilà, now the contract is satisfied.
This subtle violation often imposes a client with the ability to tell the difference between concrete objects employed. For example, given the first Account’s contract, it could look like the following:
class Client
{
public function go(Account $account, Money $money)
{
if ($account instanceof DefaultAccount && !$account->hasEnoughMoney($money)) {
return;
}
$account->withdraw($money);
}
}
And, this automatically violates the open-closed principle [that is, for money withdrawal requirement. Because you never know what happens if an object violating the contract doesn't have enough money. Probably it just returns nothing, probably an exception will be thrown. So you have to check if it hasEnoughMoney() -- which is not part of an interface. So this forced concrete-class-dependent check is an OCP violation].
This point also addresses a misconception that I encounter quite often about LSP violation. It says the “if a parent’s behavior changed in a child, then, it violates LSP.” However, it doesn’t — as long as a child doesn’t violate its parent’s contract.
Liskov Substitution Principle
Inheritance Subtyping
Wiki Liskov substitution principle (LSP)
Preconditions cannot be strengthened in a subtype.
Postconditions cannot be weakened in a subtype.
Invariants of the supertype must be preserved in a subtype.
*Precondition + Postcondition = function (method) types[Swift Function type. Swift function vs method]
//Swift function
func foo(parameter: Class1) -> Class2
//function type
(Class1) -> Class2
//Precondition
Class1
//Postcondition
Class2
Example
//C3 -> C2 -> C1
class C1 {}
class C2: C1 {}
class C3: C2 {}
Preconditions(e.g. function parameter type) can be the same or weaker(strives for -> C1)
Postconditions(e.g. function returned type) can be the same or stronger(strives for -> C3)
Invariant variable[About] of super type should stay invariant
Swift
class A {
func foo(a: C2) -> C2 {
return C2()
}
}
class B: A {
override func foo(a: C1) -> C3 {
return C3()
}
}
Java
class A {
public C2 foo(C2 a) {
return new C2();
}
}
class B extends A {
@Override
public C3 foo(C2 a) { //You are available pass only C2 as parameter
return new C3();
}
}
Behavioral subtyping
Wiki Liskov substitution principle (LSP)
Contravariance of method parameter types in the subtype.
Covariance of method return types in the subtype.
New exceptions cannot be thrown by the methods in the subtype, except if they are subtypes of exceptions thrown by the methods of the supertype.
Let me try, consider an interface:
interface Planet{
}
This is implemented by class:
class Earth implements Planet {
public $radius;
public function construct($radius) {
$this->radius = $radius;
}
}
You will use Earth as:
$planet = new Earth(6371);
$calc = new SurfaceAreaCalculator($planet);
$calc->output();
Now consider one more class which extends Earth:
class LiveablePlanet extends Earth{
public function color(){
}
}
Now according to LSP, you should be able to use LiveablePlanet in place of Earth and it should not break your system. Like:
$planet = new LiveablePlanet(6371); // Earlier we were using Earth here
$calc = new SurfaceAreaCalculator($planet);
$calc->output();
Examples taken from here
Let q(x) be a property provable about objects of x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T.
Actually, the accepted answer is not a counterexample for the Liskov principle. A square naturally is a specific rectangle, so it makes perfect sense that inherits from the class rectangle. You simply need to implement it in this way:
@Override
public void setHeight(double height) {
this.height = height;
this.width = height; // since it's a square
}
@Override
public void setWidth(double width) {
setHeight(width);
}
So, having provided a good example, this, however, is a counterexample:
class Family:
-- getChildrenCount()
class FamilyWithKids extends Family:
-- getChildrenCount() { return childrenCount; } // always > 0
class DeadFamilyWithKids extends FamilyWithKids:
-- getChildrenCount() { return 0; }
-- getChildrenCountWhenAlive() { return childrenCountWhenAlive; }
In this implementation, DeadFamilyWithKids cannot inherit from FamilyWithKids since getChildrenCount() returns 0, while from FamilyWithKids it should always return something greater 0.