7

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?

  • I went through that, but it didn't answer my query. I have read lot of answers actually, but no help so far. – user270386 May 3 '18 at 8:40
  • 1
    it's right there in the top answer, have you read it: Every time you derive one class from another, think about the base class and what people might assume about it... Then think "do those assumptions remain valid in my subclass?" If not, rethink your design. – gnat May 3 '18 at 8:52
  • @gnat, Yeah I did, but I am little slow :) And I usually need more explanation than others might require. After reading David Arno's thorough answer, I am able to relate to that line now. – user270386 May 3 '18 at 10:36
  • 1
    @FrankHileman many of us work with compilers and code bases that are less than ideal. Even if we didn't it's still a good thing when the humans also understand how to respect them. – candied_orange May 3 '18 at 18:10
10

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.

  • Perhaps a good addition to this answer would be an example of behaviour that could be added to such a delete function that would be an LSP violation? – Andy Hunt May 3 '18 at 8:25
  • 1
    @user270386: No. LSP is not about the structure of the subclass, it's about the behavior of the subclass code, compared to the behavior of the base class. It's not the extra functionality by itself that violates the history constraint; rather, the constraint is violated if this new functionality does something that's unexpected (e.g. prohibited) in the base class (and this includes things that can be expressed through the language, as well as things that can only be expressed through documentation). – Filip Milovanović May 3 '18 at 9:07
  • 1
    It may be worth noting that if T is a strict interface, fully abstract class, or even the null object pattern then S is pretty much about the structure. T is code. It's not necessarily a specification, a requirements document, or a product owner. We only care about LSP violations when T is being used to manifest a constraint that must always hold. Not every class does that. But If you tell S to delete and by design S does nothing that had better be OK with the rest of the code. T can't tell you if that's true. It can only tell you that you'd better check before you do this. – candied_orange May 3 '18 at 10:28
  • 1
    (continued) IMO, it's the requirement for substitutability that drives all this. Substitutability really stems from the ability to treat two different implementations (behaviors) as equivalent at some higher level of abstraction prescribed by the contract defined by the supertype. Structure is, in that sense, a means to an end. – Filip Milovanović May 3 '18 at 11:26
  • 1
    @DavidArno "the code of the base type" -- is neither a specification, nor accessible necessarily. – Frank Hileman May 3 '18 at 16:38
3

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.