2 ms·
Yes, 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,
by sunir 17y ago
Yes, 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.
- deleted 17y ago[deleted]
- DougBTX 17y ago> This can happen with any implementation of any class. From the definition of a square (a polygon with four sides of equal length) and we are trying to infer behaviour. But the definition does not require a square to remain a square after transformation; a square with a skew applied need not be a square nor a rectangle. Treating the square as a concrete class-based entity cannot capture this reality. You are correct, this could happen with any class.
- sunir 17y ago"If two of them were Squares, I would expect height and width to mirror each other. Your saying you wouldn't expect it." No, I wouldn't expect it, because I don't know about Squares. Don't be misled because this example are uses shapes we all learn in kindergarten. The real lesson is about all the abstractions we make up all day long building applications in the real world. Think of it as Frobulator and the false subclass MagicFrobulator. Caches happen everywhere. Going back to squares and rectangles, I stated the collection was a column. Each rectangle rests on the one before it in the collection. When I narrow the column, I set the width of every Rectangle in the collection, but now unbeknownst to me some elements have changed their height. If the column happened to be in a collection of columns sorted by height, it would now be out of sort order. The original programmer would make a reasonable assumption (well, duh) that the column sort order would not be affected by a SetWidth call. "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." The definition of Rectangle states that changing the width would not change the height. I don't understand your objection. I think using something as familiar as shapes is confusing the point. Again, go back to Frobulator and MagicFrobulator. In 2005, I invent Frobulator which has two properties foo and bar that are mutually exclusive. I release Frobulator to the world and make a million dollars. In 2008 you have a revelation that you could make a billion dollars if you could just simplify Frobulator. You decide that you only really need foo, and you can kind of obsolete bar by making it dependent on foo. You then market this as MagicFrobulator with the pitch, "Throw out your Frobulators! MagicFrobulator is the perfect replacement." Customers buy your MagicFrobulators and plug them in and discover to their horror that they explode in a shower of Star Trek induced sparks. As it turns out, other systems have regulated themselves by setting and getting foo and bar independently. Now, Widgets set foo and screw up Gadgets who did not expect bar to have changed. Bar just happens to control the amperage, and so the Gadgets explode.
- raganwald 17y agoSo. Squares violate the assumptions that Rectangles established. Where did these "assumptions" come from? Surely the programmers are not looking at the implementation of a class rather than its interface? If we have a language and a system where you have an elaborate vocabulary for constraining the types of messages we can send to an object and the parameters we can associate with those methods but we have absolutely zero ways to constrain what those messages will return and what changes of state those messages will cause... Well then there are going to be these kinds of arguments. I think tools can help here. For one thing, we could use languages like Eiffel that give us Design by Contract that is checked by the compiler. If setting the width is not supposed to set the height, we can make that part of the contract for the setWidth(...) method of a Rectangle and our compiler will not allow us to write a Square class that is-a Rectangle. And alternate approach with less strict languages is to write tests for Rectangle. I'm a big fan of Strict Liskov Equivalence (http://weblog.raganwald.com/2008/04/is-strictly-equivalent-to.html http://weblog.raganwald.com/2008/04/is-strictly-equivalent-t...). In a project with tests, I believe that any test written for the Rectangle class should work for instances of all of its subclasses. Therefore if we write a test for the rectangle class and we include a check that setting width does not affect height, then a Sqauare cannot be a Rectangle whether the compiler barks at us or not. The test in our project establishes that expectation for all Rectangle subclasses. OTOH, if no such test exists, than the programmer making that assumption is wrong. She should either drop the assumption or add the appropriate tests to the project to document her understanding of the contract of a Rectangle.