What happened to the overlay of the square and the rectangle?

I read an article on practice, according to which the Rectangle class inheritance class class is bad practice, saying that it violates the LSP (Liskov replacement principle). I still do not understand, I made a code example in Ruby:

class Rectangle attr_accessor :width, :height def initialize(width, height) @width = width @height = height end end class Square < Rectangle def initialize(length) super(length, length) end def width=(number) super(number) @height = number end def height=(number) super(number) @width = number end end s = Square.new(100) s.width = 50 puts s.height 

Can someone tell me what's wrong with him?

+6
source share
3 answers

I’m not always keen on Liskov, because he seems to limit what you can do with inheritance based on behavior and not on “essence”. In my opinion, inheritance has always been intended as an “is” relationship, rather than “acting exactly the same”.

Having said that the wikipedia article goes into detail about why some people think this is bad using your exact example:

A typical example that violates the LSP is the Square class, which comes from the Rectangle class, assuming that the getter and setter methods exist both in width and in height.

A square class always assumes that the width is equal to the height. If the Square object is used in the context where the Rectangle is expected, unexpected behavior may occur, since the size of the square cannot (or rather should not) be changed independently.

This problem cannot be easily fixed: if we can modify the setter methods in the Square class to keep the square invariant (i.e. keep the dimensions equal), then these methods weakened (violated) the postconditions for the rectangle that claim that the sizes can be changed independently.

So, looking at your code along with the equivalent Rectangle code:

 s = Square.new(100) r = Rectangle.new(100,100) s.width = 50 r.width = 50 puts s.height puts r.height 

the output will be 50 on the left and 100 on the right.

But, this is an important bit from the article, in my opinion:

Violations of an LSP, like this one, may or may not be in practice, depending on the postconditions or invariants that are actually expected from code that uses classes that violate the LSP.

In other words, if code using classes understands behavior, there is no problem.

The bottom line, the square is the correct subset of the rectangle, for the free definition of the rectangle :-)

+4
source

What is associated with it with the Liskov substitution principle (LSP) is that your Rectangle and Square mutable. This means that you need to explicitly override setters in the subclass and lose the benefits of inheritance. If you make the Rectangle immutable, i.e. If you want another Rectangle , you create a new one, and not change the dimensions of an existing one, then there is no problem with breaking the LSP.

 class Rectangle attr_reader :width, :height def initialize(width, height) @width = width @height = height end def area @width * @height end end class Square < Rectangle def initialize(length) super(length, length) end end 

Using attr_reader gives getters, but does not set, therefore, immutability. With this implementation, both Rectangles and Squares provide visibility for height and width , for the square they will always be the same, and the concept of the area is consistent.

+2
source

Consider an abstract base class or interface (whether something is an interface or an abstract class, implementation detail not related to LSP) ReadableRectangle ; It has read-only Width and Height properties. From this one could get the ReadableSquare type, which has the same properties, but on the contract guarantees that Width and Height will always be equal.

From the ReadableRectangle you can determine the specific type ImmutableRectangle (which takes height and width in its constructor and ensures that the Height and Width properties always return the same value) and MutableRectangle . You can also define a specific type of MutableRectangle , which allows you to set the height and width at any time.

On the "square" side of things, ImmutableSquare should be replaced for both ImmutableRectangle and ReadableSquare . A MutableSquare , however, is replaced only with a ReadableSquare [which, in turn, is substitutable for a ReadableRectangle .] In addition, although the behavior of a ImmutableSquare substitutable for ImmutableRectangle , the value obtained by inheriting a specific type of ImmutableRectangle will be limited. If the ImmutableRectangle was an abstract type or interface, the ImmutableSquare class ImmutableSquare need to use only one field, and not two, to keep its size (for a class with two fields, saving one does not matter much, but it's not difficult to imagine classes with a lot more fields where savings can be significant). If, however, ImmutableRectangle is a concrete type, then any derived type must have all the fields of its base.

Some types of squares are replaced for the corresponding types of rectangles, but a mutable square cannot be replaced for a mutable rectangle.

0
source

Source: https://habr.com/ru/post/946053/


All Articles