I’m at the half-way point in my writing about SOLID technics. This issue, I look at the Liskov Substitution Principle (LSP). This is the only SOLID principle named after the person that originally promoted the concept. In 1998, Barbara Liskov, put forth this principle in SIGPLAN Notices. Here’s what she said, “What is wanted here is something like the following substitution property: If for each object o2 of type T such that for all programs P defined in terms of T, the behavior or P is unchanged when o1 is substituted for o2 then S is a subtype of T.”
This article is published from the DNC Magazine for .NET Developers and Architects. Download this magazine from here [Zip PDF] or Subscribe to this magazine for FREE and download all previous and current editions
Editorial Note: You can read about Single Responsibility Principle and Open-Closed Principle over here https://www.dotnetcurry.com/software-gardening/1148/solid-single-responsibility-principle and https://www.dotnetcurry.com/software-gardening/1176/solid-open-closed-principle .
What Barbara Liskov specifically meant by this has been debated for years. You can follow some of this at http://c2.com/cgi/wiki?LiskovSubstitutionPrinciple, where Robert “Uncle Bob” Martin, Alistair Cockburn, Michael Feathers, and others discuss the real meaning of Liskov Substitution Principle and even if LSP is a real OOP principle. I’m not going to debate whether LSP is or is not a real principle of Object Orientation. I’m going to follow the general belief that it is. And I’m going to give you the generally accepted definition.
Uncle Bob summarized LSP as “Subtypes must be substitutable for their base types”. In other words, given a specific base class, any class that inherits from it, can be a substitute for the base class. The classic example of this shows how you cannot substitute a rectangle class for a square class.

class LSPDemo
{
static void Main(string[] args)
{
Rectangle shape;
shape = new Rectangle();
shape.SetWidth(14);
shape.SetHeight(10);
Console.WriteLine("Area={0}", shape.Area); // 140
shape = new Square();
shape.SetWidth(14);
shape.SetHeight(10);
Console.WriteLine("Area={0}", shape.Area); // 100
Console.ReadLine();
}
}
public class Rectangle
{
protected int _width;
protected int _height;
public int Width
{
get { return _width; }
}
public int Height
{
get { return _height; }
}
public virtual void SetWidth(int width)
{
_width = width;
}
public virtual void SetHeight(int height)
{
_height = height;
}
public int Area
{
get { return _height * _width; }
}
}
public class Square : Rectangle
{
public override void SetWidth(int width)
{
_width = width;
_height = width;
}
public override void SetHeight(int height)
{
_width = height;
_height = height;
}
}
Running this code will result in the first shape having an area of 140 and the second an area of 100. You’d then be tempted to fire up the debugger to figure out what’s going on. The behavior of the subclass has changed. Using Uncle Bob’s words, the code violates LSP since subtype is not substitutable for its base type. Now, we all know that a square and rectangle are not the same, but this shows that a square is not a special type of a rectangle.
This is where most discussions of LSP stop. But LSP is more complex than this brief example. There are several rules that LSP actually enforces. These rules fall into two categories, contract rules and variance rules. Let’s make a more in depth examination of these rules.
Contract rules
When creating a class, the contract states in formal terms how to use the object. You expect the name of the methods and the parameters and data types of those parameters as input, and then what data type to expect as a return value. LSP places three restrictions on contract rules:
· Preconditions cannot be strengthened by the subtype – Preconditions are the things required by the method to run reliably. This would require the class get instantiated correctly and required parameters are passed to the method. Typically, guard clauses are used to enforce that parameters are of the correct values.
· Postconditions cannot be weakened in the subtype – Postconditions verify the object is left in a reliable state when the method returns. Guard clauses are again used to enforce postconditions.
· Invariants of the supertype must be preserved by the subtype – Invariants are things that must remain true during the lifetime of the object once object construction is finished. This could be a field value that gets set in the constructor and is assumed to not change. The subtype should not change these type of fields to invalid values. For example, there may be a business rule that minimum shipping charge field must be greater than or equal to zero. The subtype should not make it negative. Read-only fields guarantee this rule is followed.
Variance rules
Before explaining variance rules, we need to define variance. Here’s what Wikipedia says, “Variance refers to how subtyping between more complex types relates to subtyping between their components…The variance in a C# interface is determined by in/out annotations on its type parameters” https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science). That was rather complicated. Think of it like this, how do you expect different, but related subtypes to behave? That is variance. Now, here are the LSP variance rules:
· There must be a contravariance of method arguments in the subtype – The subtype reverses the ordering of types.
· There must be covariance of return types from the method in the subtype – The subtype keeps types in the same order, from specific to most generic.
· The subtype should not throw new exceptions unless those exceptions are subtypes of exceptions thrown by the base type – This rule is easy and self-explanatory.
If you extrapolate these rules, you find that a subtype cannot accept a more specific type as an argument and cannot return a less specific type as the result.
A solution to Liskov
Now that I’ve shown what LSP is not and explained what it is, I would like to show you an example of what the Liskov Principle is. The problem is, we need to substitute square for rectangle, or vice-versa, with only changing the class that gets instantiated. But, when creating a square, if we pass two parameters for height and width, which parameter is the correct one? We could require that you pass the same number twice, but that seems rather useless as only one is needed. What this comes down to is that the above code is good to show what Liskov is not, but doesn’t work well when you try to show what Liskov is. So, I have to turn to a different example.
I’ve decided to use a base type of Animal. Granted, not a perfect example as some animals hop, others slither, walk, or gallop. Some fly, others don’t. Some have feet, others don’t. But I think it will still show how to substitute one subclass for another.
class Program
{
static void Main(string[] args)
{
Animal animal = new Dog();
Console.WriteLine(animal.Walk());
Console.WriteLine(animal.Run());
Console.WriteLine(animal.Fly());
Console.WriteLine(animal.MakeNoise());
Console.ReadLine();
}
}
public class Animal
{
public string Walk()
{
return "Move feet";
}
public string Run()
{
return "Move feet quickly";
}
public virtual string Fly()
{
return null;
}
public virtual string MakeNoise()
{
return null;
}
}
public class Dog : Animal
{
public override string MakeNoise()
{
return "Bark";
}
}
public class Bird: Animal
{
public override string MakeNoise()
{
return "Chirp";
}
public override string Fly()
{
return "Flag wings";
}
}
If you run this code for Dog, you get the following output.
Move feet
Move feet quickly
Bark
Note the blank line for Fly( ). Dogs don’t fly, so nothing gets returned. Now change Dog to Bird and you get
Move feet
Move feet quickly
Flag wings
Bark
What happens if you now change Bird to Animal?
Move feet
Move feet quickly
A subclass (Dog or Bird) can be substituted for the base class (Animal) and everything still works. The code itself doesn’t care. We could also take this one step further and use a Factory method to create the class we want to use, and the code wouldn’t even know the class at all. But that’s beyond this discussion.
So now you know about Liskov Substitution Principle. It’s well defined rules for using subtypes in place of the base type. By following these rules, and others in SOLID, you will have better software that is more maintainable, easier to extend, and less fragile. Your software garden will be lush, green, and thriving.
About Software Gardening
Comparing software development to constructing a building says that software is solid and difficult to change. Instead, we should compare software development to gardening as a garden changes all the time. Software Gardening embraces practices and tools that help you create the best possible garden for your software, allowing it to grow and change with less effort. Learn more in What is Software Gardening.
This article has been editorially reviewed by Suprotim Agarwal.
C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.
We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).
Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.
Click here to Explore the Table of Contents or Download Sample Chapters!
Was this article worth reading? Share it with fellow developers too. Thanks!
Craig Berntson is a software architect specializing in breaking up the monolith and improving developer teams and processes. He is the author of two books on software development and has been a speaker at conferences across North America and Europe. He received the Microsoft MVP award twenty-two straight years. He lives in Salt Lake City, Utah.