7 ms·
I understand the author is trying to show pitfalls of OO design. But the square/rectangle example doesn't do it for me. 1 - "There are actually several proble
by jhancock 17y ago
I understand the author is trying to show pitfalls of OO design. But the square/rectangle example doesn't do it for me.
1 - "There are actually several problems here. Thread safety is one, but let’s assume the class doesn’t need to be thread-safe."
The Rectangle implementation has a thread issue without regard to the Square subclass:
public double getPerimeter() {
return 2*width + 2*height;
}
2 - "In object oriented programming, it is necessary that a subclass be able to fulfill the contract of its superclass. In this case, that means the square has to respond to setHeight() and setWidth() calls. However doing so enables the square to violate the nature of a square. A square cannot stand in for a rectangle.
You can try to work around this by overriding the setHeight() and setWidth() methods. For example, one might call the other:...
However this is fundamentally unsatisfying because there is no reasonable expectation that calling one of setHeight() on a Rectangle object will also invoke the setWidth() method or vice versa."
This is where things unwind. It is perfectly expected that calling setHeight() on a Square will also change its width.
3 - "We could instead just forbid setHeight() and setWidth() completely by throwing UnsupportedOperationException"
Yep, you could do that and it does show you are doing more work in working around using Square as a subclass of Rectangle. So either don't subclass it or accept the fact that its perfectly normal that if you have a Square, changing either its width or height will effect the other.
The author is trying too hard here. Its decent writing and a good attempt to show how subclassing can lead to unintended consequences. But this is not a great example.
- txxxxd 17y ago"It is perfectly expected that calling setHeight() on a Square will also change its width." If you're writing code that operates on Rectangles you wouldn't expect setHeight to have the side-effect of changing the width too. It's only "perfectly expected" if you know about the implementation details of Square, which may not even have existed until after a lot of Rectangle code was already in place.
- jhancock 17y agoyour correct. And this is why the example is not good. Your saying someone might not expect the "side effect" of width changing when height changes. But this is a semantic setup. People do know how a Square is supposed to behave. One of the characteristics of a "first class object" is that there is common agreement on behavior. If we don't have this shared agreement, lots of things about OO design fall apart.
- endtime 17y ago>It's only "perfectly expected" if you know about the implementation details of Square, which may not even have existed until after a lot of Rectangle code was already in place. I don't quite agree. You don't need to know implementation details of Square (e.g. does SetHeight set the height and call SetWidth, or does it just call SetSide? Or does it, as well as SetWidth, set some new property called side?), you just need to know how a Square differs from a Rectangle (width == height). Because Squares are a subset of Rectangles, any Rectangle code should also work with Squares. Even without Square, prevents you instantiating a Rectangle and always calling SetWidth(x) and SetHeight(x) one after the other.
- mattmcknight 17y agoIf the method isn't final, I would consider the possibility of implementation differences in subclasses. I am trying to think of a case where it would cause a problem, and most of those involve methods that probably ought to be in the Rectangle class anyway (and thus could be overridden in square). To me the cleanest solution is : setDimensions(height, width) where the contract for square enforces the equality of height and width.
- sunir 17y agoYes, it's true that "it is perfectly expected that calling setHeight() on a Square will also change its width," which is why Square has its own class. However, if Square is subclassed from Rectangle, some Squares will be called in a context where setHeight is intended to be called on a Rectangle. Consider if you have a collection of rectangles, and you called SetWidth on all of them (say you selected a column of rectangles and wanted to narrow them), you would not expect that two out of nine of your collection also changed its height. If you have cached computations that depend on the rectangles' height (e.g. totalHeight of the column), your cached computations are now wrong and you are none the wiser. Sure at this point we both know that there is a Square subclass of Rectangle that does different things, and we know I threw in a couple Squares into my array of Rectangles, but the key point is this: The programmer who created the array of Rectangles might not know about Squares. The Square class could have been written much later than her code, and suddenly you have violated the assumptions she was relying on, and guess who is going to be blamed for breaking the app?
- jhancock 17y agoI see what your getting at. But this whole example is a setup. Let me go through some of your arguments to show just how easily they can be turned. 1 - "you would not expect that two out of nine of your collection also changed its height." If two of them were Squares, I would expect height and width to mirror each other. Your saying you wouldn't expect it. Well, that is a problem if two different programmers using the same classes have two different views of the object's behavior. This can happen with any implementation of any class. 2 - "If you have cached computations that depend on the rectangles' height" Your violating encapsulation of the object's attributes by "caching" this data elsewhere. If you need to do such things, this is what MVC was invented to handle: an attribute changes, it fires an event and dependents refresh themselves, caches invalidate, etc. 3 - "The Square class could have been written much later than her code" Its still a Square (by my definition). The new Squares she has are still Rectangles too. Why did this programmer assume that changing a rectangle's length would never effect its width? This invalid assumption could have been made prior or after the introduction of Squares. In short, the original argument is bad because almost everyone agrees about the behavior of a Square. Key to use of any code is agreement on how it works. For example, If I use jQuery and can't wrap my mind on how John Resig thinks I should be using his library, its my problem.
- vlisivka 17y agoIf you use setFoo() and getFoo() then you are not using OOP at all. Encapsulation is simple thing - just write setters and getters for all your internal data and you done. ;-) PS. «If the classes are immutable, there is no problem with this state of affairs. Circles satisfy all invariants of ellipses; and an (immutable) circle can be used in any context where an immutable ellipse is expected.»
- drp 17y agoI agree. You can't have a rectangle with a null width, so why should you be able to instantiate one?