DotNetCurry Logo

SOLID: Liskov Substitution Principle (Software Gardening)

Posted by: Craig Berntson , on 1/11/2016, in Category Software Gardening
Views: 7594
Abstract: Liskov Substitution Principle (LSP) is a SOLID principle that says that given a specific base class, any class that inherits from it, can be a substitute for the base class. We will explore LSP in this article.

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 http://www.dotnetcurry.com/software-gardening/1148/solid-single-responsibility-principle and http://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.

liskov-substitution

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.

Was this article worth reading? Share it with fellow developers too. Thanks!
Share on Google+
Further Reading - Articles You May Like!
Author
Craig Berntson works for one of the largest mortgage companies in the US where he specializes in middleware development and helping teams get better. He has spoken at developer events across the US, Canada, and Europe for over 20 years and is a Grape City Community Influencer. Craig is the coauthor of 'Continuous Integration in .NET' available from Manning. He has been a Microsoft MVP since 1996. Craig lives in Salt Lake City, Utah. Email: dnc@craigberntson.com Twitter: @craigber.


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!