Liskov's substitution principle : If subtype has some extra behaviour implemented, which is not present in type, then is this violation of LSP?

softwareengineering.stackexchange https://softwareengineering.stackexchange.com/questions/370376

Question

In my quest to write better, cleaner code, I am learning about SOLID principles. In this, LSP is proving to be little difficult to grasp properly.

My doubt is what if I have some extra methods in my subtype, S, which were not there in type, T, will this always be violation of LSP? If yes, then how do I extend my classes?

For example, lets say we have a Bird type. And its subtypes are Eagle and Humming Bird. Now both the subtypes have some common behavior as the Bird. But Eagle also has good predatory behaviour (which is not present in general Bird type), that I want to use. Hence, now I won't be able to do this :

Bird bird = new Eagle();

So is giving Eagle those extra behaviour breaking LSP ?

If yes, then that means I can't extend my classes because that would cause LSP violation?

class Eagle extends Bird {
   //we are extending Bird since Eagle has some extra behavior also
}

Extending classes should be allowed in accordance with Open/Closed principle right?

Thank you in advance for answering ! As you can clearly see, LSP has got me confused like anything.

Edit: Refer this SO answer. In this again, when Car has additional behaviour like ChangeGear, it violates LSP. So, then how do we extend a class, without violating LSP?

Was it helpful?

Solution

My doubt is what if I have some extra methods in my subtype, S, which were not there in type, T, will this always be violation of LSP?

Very simple answer: no.

The point to the LSP is that S should be substitutable for T. So if T implements a delete function, S should implement it too and should perform a delete when called. However, S is free to add additional functionality over and above what T provides. Consumers of a T, when given an S would be unaware of this extra functionality, but it's allowed to exist for consumers of S directly to utilise.

A highly contrived of examples of how the principle can be violated might be:

class T
{
    bool delete(Item x)
    {
        if (item exists)
        {
            delete it
            return true;
        }
        return false;
    }
}

class S extends T
{
    bool delete(Item x)
    {
        if (item doesn't exist)
        {
            add it
            return false;
        }
        return true;
    }
}

Slightly more complex answer: no, as long as you don't start affecting the state or other expected behaviour of the base type.

For example, the following would be a violation:

class Point2D
{
    private readonly double _x;
    private readonly double _y;

    public virtual double X => _x;
    public virtual double Y => _y;

    public Point2D(double x, double y) => (_x, _y) = (x, y);
}

class MyPoint2D : Point2D
{
    private double _x;
    private double _y;

    public override double X => _x;
    public override double Y => _y;

    public MyPoint2D(double x, double y) : 
        base(x, y) => (_x, _y) = (x, y);

    public void Update(double x, double y) => (_x, _y) = (x, y);
}

The type, Point2D, is immutable; its state cannot be changed. With MyPoint2D, I've deliberately circumvented that behaviour to make it mutable. That breaks the history constraint of Point2D and so is a violation of the LSP.

OTHER TIPS

Of course not. If the Eagle object can be used by any code that expects a Bird or subclass, and behaves as a Bird should behave, you are fine.

Of course the Eagle behaviour can only be used by code that is aware that it is such an object. We would expect that some code will explicitly create an Eagle object and use it as an Eagle object, while being able to use any code that expects Bird objects.

Licensed under: CC-BY-SA with attribution
scroll top