0

Consider the classic example in Java

// Violation of Likov's Substitution Principle
class Rectangle
{
    protected int m_width;
    protected int m_height;

    public void setWidth(int width){
        m_width = width;
    }

    public void setHeight(int height){
        m_height = height;
    }


    public int getWidth(){
        return m_width;
    }

    public int getHeight(){
        return m_height;
    }

    public int getArea(){
        return m_width * m_height;
    }   
}

class Square extends Rectangle 
{
    public void setWidth(int width){
        m_width = width;
        m_height = width;
    }

    public void setHeight(int height){
        m_width = height;
        m_height = height;
    }

}

class LspTest
{
    private static Rectangle getNewRectangle()
    {
        // it can be an object returned by some factory ... 
        return new Square();
    }

    public static void main (String args[])
    {
        Rectangle r = LspTest.getNewRectangle();

        r.setWidth(5);
        r.setHeight(10);
        // user knows that r it's a rectangle. 
        // It assumes that he's able to set the 
        //   width and height as for the base class

        System.out.println(r.getArea());
        // now he's surprised to see that the area is 100 instead of 50.
    }
}

I think the whole point is that object of child class can be treated(type casting?) like parent class in static typed language like Java:

Rectange rectange = new Square();

but in Ruby I don't think that make any sense, same thing in Ruby:

class Rectangle
    attr_accessor :width, :height

    def getArea()
       @width * @height
    end
end

class Square < Rectangle

    def width=(number)
        super(number)

        @height = number
    end

    def height=(number)
        super(number)
        @width = number
    end
end

s = Square.new(100)

puts s.class

s.width = 50

puts s.height

become I can alway check the class of the object by using:

rectange.class

in this case, if the code in Ruby it will return Square

so I won't treat it like Rectange.

Could anyone explain me the point for applying LSP in dynamic typed language like Ruby?


Although I still don't know what problem it may cause to let Square be a child of Rectangle. but now I learn to tell whether it violet the LSP or not by using the way:

for square object is an instance of Square Class, rectangle object is an instance of Rectangle class

the width= is a method in both of them

width= in square can not be replaced by width= in rectangle

because it won't set the height as the one defined in 'square' does.

Am I wrong with this way of thinking?

And Also I learned to use the violet the contact of 'width=' method in the Rectangle Class to analysis this problem:

for width= in 'Rectangle' class

precondition: @width and @height has some value.

postconditon: @width change to the new value, @height remain the untouched.

for 'width=' in Square class

precondition: same as above

postconditon: '@width' change to the the new value, @height change to the new value

according to the principle : require no more, promise no less

the @height is changed, so promise is not fulfilled, thus it can't be inheritance

Does anyone can give me some advice about my way of analysis this problem by using DBC?

mko
  • 21,334
  • 49
  • 130
  • 191
  • So a method `m` that expects a Rectangle needs to know about the Square special case even though there was no such thing as a Square when `m` was written? A better example of violating LSP would be making `height=` a private method in Square. And in Ruby you'd usually be talking about something with `width=`, `height=`, `getArea`, ... methods than a Rectangle. – mu is too short Jun 12 '13 at 23:43
  • @mu is too short: More that replacing `Rectangle` with `Square` should not violate the correctness of the program (which happens in this example), not that all code needs to "know" about a special case that didn't exist when that code was written. – mipadi Jun 12 '13 at 23:47
  • @mipadi: I mention the `m` example because the OP mentions checking `rectange.class` as a way around the problem. The whole "square is a rectangle" example is terrible anyway as is using a class that is little more than a `struct`. – mu is too short Jun 12 '13 at 23:51
  • @mu is too short: True, the solution as described isn't really a "solution"; the example is relevant, though, and pretty commonly used to illustrate LSP. – mipadi Jun 12 '13 at 23:54
  • @mipadi: Commonly used perhaps but still terrible because `Square < Rectangle` and `Rectangle < Square` are both equally valid and that makes a big mess of things. – mu is too short Jun 12 '13 at 23:58
  • @mu is too short: The point of the example is to show applications of LSP, and why ignoring it causes problems. Your observation is, in fact, one of the primary lessons of the example; in terms of OOP, a Square is *not* necessarily a rectangle and shouldn't inherit from Rectangle, or it will violate LSP. – mipadi Jun 13 '13 at 00:01
  • An immutable `Square` is an immutable `Rectangle`. A "write-only" `Rectangle` is a "write-only" `Square`. A read/write `Square` and a read/write `Rectangle` are neither sub- nor supertypes of each other. – Jörg W Mittag Jun 13 '13 at 00:32
  • Hi all, thanks for your comments, I've added some my analysis process of telling whether it violet the LSP or not. Could you guy give me some insight about it? – mko Jun 13 '13 at 08:39

1 Answers1

2

LSP still applies, even in dynamically-typed languages like Ruby. Your reasoning:

I can alway check the class of the object by using:

rectange.class

in this case, if the code in Ruby it will return Square so I won't treat it like Rectangle.

Isn't specific to Ruby; you can, in fact, check the actual class of the variable in Java, too. Doing so doesn't provide a "solution" to this problem.

The key observation from this example is that, while a square is a rectangle in geometry, a Square isn't a Rectangle from an OOP standpoint—a Square's behavior is incompatible with that of a Rectangle.

Community
  • 1
  • 1
mipadi
  • 398,885
  • 90
  • 523
  • 479
  • could you use a simple example to show why **Square**'s behavior is incompatible with that of a Rectangle as you mention? – mko Jun 13 '13 at 00:21
  • @yozloy: The example itself demonstrates that: `Square.height=` and `Square.width=` do not behave as expected (i.e., as they do in `Rectangle`), thus making `Square.area` return an unexpected value. – mipadi Jun 13 '13 at 00:24
  • I've updated my question, Am I correct about the analysis process? – mko Jun 13 '13 at 08:39