blog.itcode.devblog.itcode.dev

[OOP] Five Principles of Object Orientation (SOLID) - Liskov Substitution Principle (LSP)

The Liskov Substitution Principle states that when there is a parent object and a child object that inherits from it, the child object must be able to completely substitute for the parent object in any operation that calls the parent object. In object-oriented languages, inheritance occurs between objects. Through this process, a parent/child relationship is defined. A child object inherits the characteristics of its parent object and can extend upon them. However, in this process, overreaching or extensions that stray from the intent of the object can lead to improper inheritance. The Liskov Substitution Principle recommends that, for proper inheritance, a child object's extension should fully follow the direction of the parent object.

[OOP] Five Principles of Object Orientation (SOLID) - Liskov Substitution Principle (LSP)

The Liskov Substitution Principle states that when there is a parent object and a child object that inherits from it, the child object must be able to completely substitute for the parent object in any operation that calls the parent object. In object-oriented languages, inheritance occurs between objects. Through this process, a parent/child relationship is defined. A child object inherits the characteristics of its parent object and can extend upon them. However, in this process, overreaching or extensions that stray from the intent of the object can lead to improper inheritance. The Liskov Substitution Principle recommends that, for proper inheritance, a child object's extension should fully follow the direction of the parent object.
RWB0104
@RWBwritten at 2021-08-15 04:42:11
Object-Oriented Programming

시리즈 모아보기

Object-Oriented Programming

7 / 9

The Liskov Substitution Principle states that when there is a parent object and a child object that inherits from it, the child object must be able to completely substitute for the parent object in any operation that calls the parent object.

In object-oriented languages, inheritance occurs between objects. Through this process, a parent/child relationship is defined. A child object inherits the characteristics of its parent object and can extend upon them. However, in this process, overreaching or extensions that stray from the intent of the object can lead to improper inheritance.

The Liskov Substitution Principle recommends that, for proper inheritance, a child object's extension should fully follow the direction of the parent object.

A commonly used example when explaining the Liskov Substitution Principle is the relationship between rectangles and squares.

JAVA

/**
 * Rectangle class
 *
 * @author RWB
 * @since 2021.08.14 Sat 11:12:44
 */
public class Rectangle
{
	protected int width;
	protected int height;
	
	/**
	 * Width-returning function
	 *
	 * @return [int] width
	 */
	public int getWidth()
	{
		return width;
	}
	
	/**
	 * Height-returning function
	 *
	 * @return [int] height
	 */
	public int getHeight()
	{
		return height;
	}
	
	/**
	 * Width-setting function
	 *
	 * @param width: [int] width
	 */
	public void setWidth(int width)
	{
		this.width = width;
	}
	
	/**
	 * Height-setting function
	 *
	 * @param height: [int] height
	 */
	public void setHeight(int height)
	{
		this.height = height;
	}
	
	/**
	 * Area-returning function
	 *
	 * @return [int] area
	 */
	public int getArea()
	{
		return width * height;
	}
}

Rectangle is an object that implements a rectangle. It can set and return width and height, and it can calculate its own area based on those values.

Since a square, broadly speaking, is also a type of rectangle, you figured you could quickly build the square object by inheriting from the rectangle.

JAVA

/**
 * Square class
 *
 * @author RWB
 * @since 2021.08.14 Sat 11:19:07
 */
public class Square extends Rectangle
{
	/**
	 * Width-setting function
	 *
	 * @param width: [int] width
	 */
	@Override
	public void setWidth(int width)
	{
		super.setWidth(width);
		super.setHeight(getWidth());
	}
	
	/**
	 * Height-setting function
	 *
	 * @param height: [int] height
	 */
	@Override
	public void setHeight(int height)
	{
		super.setHeight(height);
		super.setWidth(getHeight());
	}
}

As shown above, you were able to easily implement the square object Square by inheriting from Rectangle.

Unlike a rectangle, a square has equal width and height, so you overrode the setters so that setting either width or height would keep both of them in sync.

Let's calculate the area of the Rectangle you implemented.

JAVA

/**
 * Main class
 *
 * @author RWB
 * @since 2021.06.14 Mon 00:06:32
 */
public class Main
{
	/**
	 * Main function
	 *
	 * @param args: [String[]] parameters
	 */
	public static void main(String[] args)
	{
		Rectangle rectangle = new Rectangle();
		rectangle.setWidth(10);
		rectangle.setHeight(5);
		
		System.out.println(rectangle.getArea());
	}
}

OUTPUT

50

This is the code that calculates the area of Rectangle. Since the width is set to 10 and the height to 5, the area of 50 is correctly returned.

According to the Liskov Substitution Principle, a child object should be able to completely substitute for the parent object, so let's substitute Square, which inherits from Rectangle, and calculate the area.

If Square truly substitutes completely for Rectangle, the same result of 50 should be returned.

JAVA

/**
 * Main class
 *
 * @author RWB
 * @since 2021.06.14 Mon 00:06:32
 */
public class Main
{
	/**
	 * Main function
	 *
	 * @param args: [String[]] parameters
	 */
	public static void main(String[] args)
	{
		Rectangle rectangle = new Square();
		rectangle.setWidth(10);
		rectangle.setHeight(5);
		
		System.out.println(rectangle.getArea());
	}
}

OUTPUT

25

For some reason, the area returned is 25 instead of 50. Looking closely, the last call, setHeight(5), ended up setting both the width and height of the object to 5. So of course the area came out to 25. In other words, this object violates the Liskov Substitution Principle.

Thinking it over carefully, a rectangle and a square can't really have an inheritance relationship at all. While they do share characteristics of quadrilaterals, both are simply types of quadrilaterals, and neither fully encompasses the other.

When you inherit from the wrong object, or fail to extend it properly like this, the result may look normal on the surface but is not actually a correct object.

So how can we restructure this code to comply with the Liskov Substitution Principle?

The answer lies in correct inheritance and implementation. As explained earlier, it's difficult for an inheritance relationship to hold between a rectangle and a square. So instead, we should implement a more general quadrilateral object at a higher level, and have both square and rectangle inherit from it.

JAVA

/**
 * Shape object
 *
 * @author RWB
 * @since 2021.08.14 Sat 11:39:02
 */
public class Shape
{
	protected int width;
	protected int height;

	public Shape(int width, int height)
	{
		this.width = width;
		this.height = height;
	}
	
	/**
	 * Width-returning function
	 *
	 * @return [int] width
	 */
	public int getWidth()
	{
		return width;
	}
	
	/**
	 * Height-returning function
	 *
	 * @return [int] height
	 */
	public int getHeight()
	{
		return height;
	}
	
	/**
	 * Area-returning function
	 *
	 * @return [int] area
	 */
	public int getArea()
	{
		return width * height;
	}
}

We implement a quadrilateral object called Shape as shown above.

The setter methods from the original code should be removed, and values should instead only be assignable at the time of instance creation.
If setter methods exist, the state of the object could be changed at any time even after it's been defined.
This section previously contained an incorrect explanation before 2024-05-28, and has now been corrected.

JAVA

/**
 * Rectangle class
 *
 * @author RWB
 * @since 2021.08.14 Sat 11:12:44
 */
class Rectangle extends Shape
{
	/**
	 * Rectangle constructor function
	 *
	 * @param width: [int] width
	 * @param height: [int] height
	 */
	public Rectangle(int width, int height)
	{
		super(width, height);
	}
}

/**
 * Square class
 *
 * @author RWB
 * @since 2021.08.14 Sat 11:19:07
 */
class Square extends Shape
{
	/**
	 * Square constructor function
	 *
	 * @param length: [int] side length
	 */
	public Square(int length)
	{
		super(length, length);
	}
}

Above are the two quadrilaterals, Rectangle and Square, which inherit from Shape. Rectangle takes width and height as parameters when an instance is created, while Square, since all of its sides are equal, takes only a single parameter, length.

JAVA

/**
 * Main class
 *
 * @author RWB
 * @since 2021.06.14 Mon 00:06:32
 */
public class Main
{
	/**
	 * Main function
	 *
	 * @param args: [String[]] parameters
	 */
	public static void main(String[] args)
	{
		Shape rectangle = new Rectangle(10, 5);
		Shape square = new Square(5);

		System.out.println(rectangle.getArea());
		System.out.println(square.getArea());
	}
}

OUTPUT

50
25

Now that Rectangle and Square are no longer in an inheritance relationship with each other, they're no longer subject to the Liskov Substitution Principle's concerns.

The Liskov Substitution Principle recommends that an inherited object must always be able to completely substitute for its parent object without any issue. As with the rectangle and square example above, improper inheritance relationships should be removed, and code should be designed so that only relationships capable of fully substituting the parent object's behavior are used for inheritance.

To comply with the Liskov Substitution Principle, it's important to avoid overriding a parent object's general methods in ways that deviate from their original intent as much as possible.

Given that overriding a parent object's methods is often done specifically to add child-object-specific behavior on top of the same method, this is a rather tricky principle to uphold.




Thanks to @SungilJung and @splitCoding for pointing out errors in the code.

# CS# Object-Oriented Programming# Five Principles of OOP# Liskov Substitution Principle# LSP
ship
blog.itcode.dev

Notes from the π-th Alpaca

7.0.1
Developed by RWB since 2021.057th upgraded at 2026.08