Search code examples
design-patternsliskov-substitution-principle

Understanding contracts and Liskov Substitution principle


Consider the diagram :

enter image description here

Collection - an abstract class with the common part of all the others: abstract functions that put an integer in a collection and check whether the collection is empty.

Bag - a class that implements the collection as a hash, where you can find elements easily. Each element may appear more than once

I need to determine if this inheritance relationship maintain the Liskov Substitution principle.

So, i start with check if the signatures of the function are the same and they are. (put() and isEmpty()). After that, i check if the methods contracts of the two are the same, but can i have contracts to abstract class? i can't 'create' it. if they are, it's enough to say that these two class maintain the LSP? or something else is needed?


Solution

  • Introduction

    The Liskov Substitution principle is one of the 5 basic principles for good object-oriented design (AKA SOLID):

    The principle stated by Barbara Liskov says that Functions that use pointers of references to base classes must be able to use objects of derived classes without knowing it. It is a good design principle because when a function does not conform to the LSP, then it must know all the possible derivatives of the base class.

    The importance of this principle becomes obvious when you consider the consequences of violating it. Let's make an example. Suppose we are developing a program dealing with shapes. We have created the class Rectangle and, when designing class Square, we think natural of inheriting from class Rectangle:

    example of Liskov violation

    Using C++ we would write:

    class Rectangle
    {
    public:
            virtual void SetWidth(const double w) {width_=w;}
            virtual void SetHeight(const double h) {height_=h;}
            virtual double GetHeight() const {return height_;}
            virtual double GetWidth() const {return width_;}
    private:
            double width_;
            double height_;
    };
    
    class Square : public Rectangle
    {
    public:
            virtual void SetWidth(const double w) {
                Rectangle::SetWidth(w);
                Rectangle::SetHeight(w);
            }
            virtual void SetHeight(const double h) {
                Rectangle::SetWidth(w);
                Rectangle::SetHeight(h);
            }
    };
    
    void function (Rectangle& r)
    {
            r.SetWidth(5);
            r.SetHeight(4);
            assert(r.GetWidth() * r.GetHeight()) == 20);
    }
    

    As it is clear from the example, the assertion (and the Liskov project) is violated due to the fact that the Rectangle has a property (independency of the size of height and weight) which does not hold for a Square.

    To answer your question, you should reason about properties that hold for the base class but not for the derived classes. If the interface is well designed (as it seems), you won't find any and you'll be able of substituting the base class with any derived class.