|
You wrote:
"I will resume it differently: You are taking only a part of the article, without considering all the things I told, and you are saying it is different from a conclusion that you read which was written by another author."
Yes, that is correct. I am rejecting deriving a Square from a rectangle.
You wrote:
"So, if you really did read this part, you will see that my purpose was not to discuss if we should or should not consider the "is a" as a valid assumption."
The end of your introduction states:
"Considering that inheritance means an is a relationship, the example says: A Square is a Rectangle (and so, it is a sub-class of Rectangle) and then it shows how we are breaking the Liskov Substitution, as for a Rectangle it is possible to change Width and Height independently, which is not valid for a Square."
The phrase, "Considering that inheritance means an is a relationship" is what I disagree with. You wrote there are issues with that, and I get that you recognize some aspects of the problem with that. I reject that phrase as false.
I suppose you did use the word "consider", but later you justify not showing the correct solution precisely because a square is a rectangle.
You specifically mentioned the "is a" relationship later in the article too. Two paragraphs after that, you wrote a heading, "So, how do we solve the problem?". I am asserting that only one of the solutions you posited are optimal for the reasons cited in both my last message and in the book. And, you rejected showing that solutions in a paragraph near the end of the article.
You wrote:
"Note: There are other possible examples, like making a Rectangle and a Square completely independent or making the Square the base class (with only a Size property) and then making the Rectangle to be a "more versatile" Square, but in such cases we will not keep the idea that a Square is a Rectangle so, even if it solves the programming problem, I don't think it is worth showing them as the whole idea is to make the classes represent the geometry shapes."
But, my point is that the Rectangle and Square should be independant, and every argument you make after the word "but" is not relevant, and even incorrect. It is suboptimal to keep the idea that the Square is a Rectangle when determining inheritance, even though geometrically that idea is true.
You wrote:
"So, if you really did read this part, you will see that my purpose was not to discuss if we should or should not consider the "is a" as a valid assumption. I am only showing how to solve that situation keeping the "is a"."
Yes, I read that part, and it is the part I most strongly disagree with. It seems you mean that I wrote the article to argue for a suboptimal idea, and so I shouldn't point out that the idea is suboptimal.
You wrote:
"Only to make it clear, see this paragraph:
"In my opinion the Rectangle/Square situation is a "real world" comparison that simply fails in the OOP world. In special, the "is a" expression causes the problem. A Square is a Rectangle in geometry. Such is a expression is used to say that a sub-class is a kind of a base-class. But what happens if we have these two methods: DrawRectangle(Rectangle) and DrawSquare(Square) in static typed languages (C# is mainly a static typed language)?"
I am talking about the "is a" problem, saying that the same expression is used to normally say "inheritance"."
I read that too, but then you went on at the end of the article and wrote the quote above, which I repeat immediately below here:
You wrote:
"There are other possible examples, like making a Rectangle and a Square completely independent or making the Square the base class (with only a Size property) and then making the Rectangle to be a "more versatile" Square, but in such cases we will not keep the idea that a Square is a Rectangle so, even if it solves the programming problem, I don't think it is worth showing them as the whole idea is to make the classes represent the geometry shapes."
You ended that with the right idea, then a very suboptimal idea (making the Square the base class), and then ended with, "I don't think it is worth showing them as the whole idea is to make the classes represent the geometry shapes." Again, I disagree with everything after the word "but". The fact that these represent geometry shapes has nothing at all to do with rejecting the earlier designs - although the second design in that paragraph certainly should be rejected.
You wrote:
To conclude: I am completely OK with the idea that are other solutions to the Square/Rectangle situation that doesn't require inheritance, as I don't agree that if you say "is a" in the real world that means "inheritance" in programming. And that's exactly why I said that, to me, a rectangle is an IsSquare property is the better solution. The "Is a" is simply an observation of the actual state. It doesn't require a new class.
It's not just that there are other solutions, it's that the solutions that use inheritance for this problem are inherently suboptimal.
|
|
|
|
|
So, very simple:
You want a single list to hold all objects. You want to avoid casts. And you may have methods that only receive Squares, but in 90% of the cases both squares and rectangles are OK. What do you do?
I can write an entire article about that decision. But to me, having more than one class here is over-design.
-----
In fact, you don't need to answer the arguments from that point. I am only giving more details. If you want to answer something, answer only the first part.
So, continuing:
It is not that I am not capable of writing a Square and a Rectangle as completely independent entities.
But the truth is: Usually I want them to be related.
The reasons to keep them related is the use cases. If we use such type to tell the component size (here's already a problem with a Rectangle with only Width and Height and a Rectangle that also has Left and Top, but let's ignore that for the moment). We will probably already have a base class that defines its size by a Rectangle. Having a separate type to define a square size will be of no use, as such component requires a Rectangle type. But then, using inheritance (and I am talking about inheriting base components, like the UI components) you decide to make your control only work with Square sizes. So, a property on a Rectangle will be much better than a separate type. You may argue about the base component already using a Rectangle instead of something else... but if it doesn't use that type, what will it use to define it's size?
You may have a great design solution for that. You may argue that I am presenting the sub-optimal solution... and I really did that to show some possibilities to those that want to see Rectangles and Squares as classes, keeping the "is a" relationship.
In fact, I don't agree with the idea of representing "names" as classes. Once someone said to another situation that we should support type changes at run-time, and used as example that the type is a "baby", then it is a "child" and only later an adult... but it was always an human... or maybe a "Thing". The expected method calls are more important and, in fact, that's the biggest problem as people try to use "real world" examples that don't fit. They aren't "real world programming examples". They are "real world" objects translated to languages and with many mismatches. Living beings, for instance, are a terrible comparison because each person has its own DNA and even the DNA is not enough to determine all traits, as vitamins, accidents and many other factors change the final "being".
So, do you think that the Square/Rectangle with inheritance is sub-optimal. I agree. Do you believe that you should have many classes, with different interfaces and I disagree as I don't see the benefit. Classes with incompatible interfaces can't be used when the other interface is expected, even if in such a particular situation (like read-only) that's acceptable.
But I am pretty sure you will still disagree with me. You apparently want to have the perfect OOP solution that may help you avoid using more memory to store squares than I am presenting here, as my solution will present a Square with Width and Height. I don't care. I presented many situations, many possible solutions and I will continue to say that having a single Rectangle capable of identifying itself as being a Square is the best solution, as it avoids a lot of unnecessary classes and will allow to easily put all the squares and rectangles in a single list without the need for any cast.
--------
And to make it obvious, in the situations I present in the article I ask things like:
Need to be modifiable? Yes/No. Is a Rectangle(100, 100) a Square? Yes/No.
And so, let's ignore the first question. So, it is not important if it is modifiable or not.
What about a Rectangle(100, 100). I want it to be a square. I don't want to have Rectangles of (100, 100) and Squares of (100). If they are separate types, I can't give a Square(100) to a method that requires a rectangle. I can't give a Rectangle(100, 100) to a method that requires a Square. So, that's bad design in my opinion. That is, other solutions may solve other problems, but they aren't capable of solving this simple problem.
* Note: Maybe with multiple inheritance and with factories you will be able to solve that... but I am presenting a situation ignoring multiple inheritance (I am focusing mostly on .NET, even if it applies to other languages) and the idea is to keep things simple (and fast). It is possible to create a ISquare, IRectangle etc and by using a factory (if the type is immutable) create a Square or a Rectangle, in which the Square has only one property, but implements the IRectangle... but doing everything by the interface will make things slower and for simple types (like a Rectangle) it's a bad idea to do so.
modified 7-Nov-13 21:42pm.
|
|
|
|
|
You wrote:
So, very simple:
You want a single list to hold all objects. You want to avoid casts. And you may have methods that only receive Squares, but in 90% of the cases both squares and rectangles are OK. What do you do?
You can create a class called GeometryObjects (or perhaps TwoDimensionalObject, or some other name) and derive the Rectangle and Square from that class. Then store GeometryObjects in the list.
You wrote:
I can write an entire article about that decision. But to me, having more than one class here is over-design.
I consider not making them separate classes to be overdesign. Making them separate classes is simpler code and, if the Square is to behave correctly, it's probably even less code. That code will be easier to understand and have none of the problems.
The authors of the book I cited make that point too. Try writing it both ways and compare the code that uses the classes.
You wrote:
"So, continuing:
It is not that I am not capable of writing a Square and a Rectangle as completely independent entities.
But the truth is: Usually I want them to be related.
The reasons to keep them related is the use cases. If we use such type to tell the component size (here's already a problem with a Rectangle with only Width and Height and a Rectangle that also has Left and Top, but let's ignore that for the moment). We will probably already have a base class that defines its size by a Rectangle. Having a separate type to define a square size will be of no use, as such component requires a Rectangle type. But then, using inheritance (and I am talking about inheriting base components, like the UI components) you decide to make your control only work with Square sizes. So, a property on a Rectangle will be much better than a separate type. You may argue about the base component already using a Rectangle instead of something else... but if it doesn't use that type, what will it use to define it's size?
You may have a great design solution for that. You may argue that I am presenting the sub-optimal solution... and I really did that to show some possibilities to those that want to see Rectangles and Squares as classes, keeping the "is a" relationship."
I worked in image processing for several years. I wrote a RectangleI class that had special properties that are not available in most rectangle classes I have seen. I never need a square class for anything. I expect for most UI solutions, a Rectangle classes is all that is needed.
In that case, if I wanted a square UI component, I would explicitly set the values to be the same rather than to create a new class. In such programs, the values are usually computed (as in dynamic displays) or they are assigned, as in static HTML.
The reason I did this is to keep the program as simple as possible, so that if the Width and Height could differ, but they could also be the same. I never even thought about squares, although I made square images many times. I just never had to think about squares.
I did do that once for a graphical display that showed a chessboard. In that case, the UI components wanted a width and a height, so again, I didn't create a square chessboard class, I just made sure the width and height were always the same value.
I'd only make a Square class for something that always had to be a square and where the width and height never ever could differ. Then a GetSide method would make sense.
You wrote:
In fact, I don't agree with the idea of representing "names" as classes. Once someone said to another situation that we should support type changes at run-time, and used as example that the type is a "baby", then it is a "child" and only later an adult... but it was always an human... or maybe a "Thing". The expected method calls are more important and, in fact, that's the biggest problem as people try to use "real world" examples that don't fit. They aren't "real world programming examples". They are "real world" objects translated to languages and with many mismatches. Living beings, for instance, are a terrible comparison because each person has its own DNA and even the DNA is not enough to determine all traits, as vitamins, accidents and many other factors change the final "being".
The problem you are referring to is known as the problem of "granularity" in programming. How do you represent a car, for example. Do you represent how it moves, it's shape, the materials it's made of, the molecules, the atoms, etc.! Only trivial classes, or very mathematical classes, will ever really represent the object which the class partially models (or perhaps more often, the "behavior" of the object that the class models).
However, if we don't assign names to classes, what can we do! For us humans, identifiers like "adult", or perhaps "Person" are certainly better than 014185HHj55.
Polymorphism does allow type changing at run-time, although only in a limited way. I get what you mean, and I agree. Such are the limitations of the programs we write.
You wrote:
So, do you think that the Square/Rectangle with inheritance is sub-optimal. I agree. Do you believe that you should have many classes, with different interfaces and I disagree as I don't see the benefit. Classes with incompatible interfaces can't be used when the other interface is expected, even if in such a particular situation (like read-only) that's acceptable.
I wrote one very large program with dozens of classes, all with different interfaces, because the many different classes all had nothing in common with each other.
But, I expect that is not what you meant! I'm sure you would write many different classes if the problem called for that.
In the case of Square and Rectangle, it's only one class or two, and having two is probably less code and eliminates all the problems that there are with two classes. However, while I've written Rectangle classes and used various Rectangle classes other people wrote in my career, I've never written a Square class, except perhaps as to make a point.
And, in the cases you list above where Width and Height must be independent, I'd have just one class, a Rectangle class. I wouldn't use a Square class at all.
You wrote:
"But I am pretty sure you will still disagree with me. You apparently want to have the perfect OOP solution that may help you avoid using more memory to store squares than I am presenting here, as my solution will present a Square with Width and Height. I don't care. I presented many situations, many possible solutions and I will continue to say that having a single Rectangle capable of identifying itself as being a Square is the best solution, as it avoids a lot of unnecessary classes and will allow to easily put all the squares and rectangles in a single list without the need for any cast."
In general, there is no such thing as a perfect OOP solution. For just one reason that is true, the granularity issue mentioned above, but there are other reasons that is typically unattainable.
And, I'm concerned with either memory or efficiency for this particular discussion, at least not yet. I am thinking of simplicity. I want to avoid unnecessary complexity.
You wrote:
"And so, let's ignore the first question. So, it is not important if it is modifiable or not.
What about a Rectangle(100, 100). I want it to be a square. I don't want to have Rectangles of (100, 100) and Squares of (100). If they are separate types, I can't give a Square(100) to a method that requires a rectangle. I can't give a Rectangle(100, 100) to a method that requires a Square. So, that's bad design in my opinion. That is, other solutions may solve other problems, but they aren't capable of solving this simple problem."
Again, in the past to solve this very issue, I just used a Rectangle class. If you need the Width and Height to be different, then a Square class is useless. I'd write less code.
If you want a Square class because, for some specific situation, the Width and Height will never be different, then I'd write a Square class. For most general problems, such a class is too constrained.
But, I'd never derive a Square from a Rectangle. That requires more complexity, not less.
modified 7-Nov-13 22:38pm.
|
|
|
|
|
You agree with me and you continue to disagree with the article. You never wrote a Square class, but you did write a Rectangle class. That is, you agree that in practice having a related Square class is useless.
But as we are discussing Squares and Rectangles you disagree that only a rectangle class is enough.
As you said, maybe an extremely limited situation asks for a specific Square class that's not related to rectangles but, in most cases, what people really want is a Rectangle class that's also capable of working as a Square.
|
|
|
|
|
I did not remember the guideline for "is-a" correctly, it's not "has (or shares) the same interface", although that works too, but I think the authors stated the guideline better. I have added an "Update" comment at the end of my first message in this thread with the guideline from the book. The quote from the book with the guidelines the author selected is further below.
You wrote:
You agree with me and you continue to disagree with the article.
I disagree with some statements in the article, most importantly the sentence after "Note:" under the code. I think the article misses the most important point for the example given. However, I do not disagree with everything you wrote! Clearly you are an experienced developer and you understand the problem being discussed.
You wrote:
You never wrote a Square class, but you did write a Rectangle class. That is, you agree that in practice having a related Square class is useless.
I never wrote that is "useless".
You wrote:
But as we are discussing Squares and Rectangles you disagree that only a rectangle class is enough.
No, I wrote that, for the problems I solved, a Rectangle class was enough.
You wrote:
As you said, maybe an extremely limited situation asks for a specific Square class that's not related to rectangles but, in most cases, what people really want is a Rectangle class that's also capable of working as a Square.
Ah, I see that your previous sentence with "disagreed" was probably a mistake, because you do understand that I wrote that a Rectangle class was all I have needed for the problems I have solved.
However, assuming that by, "not related to", you mean "not derived from," I still did not write that it was an "... extremely limited situation ...", although that might be true. I do expect that rectangles are more common than squares.
From the book "C++ Coding Standards - 101 Rules, Guidelines, and Best Practices" by Herb Sutter and Andrei Alexandrescu, Item 37 "Public Inheritance is substitutability. Inherit not to reuse, but to be reused", pages 64-65. (The authors explain what the title means - I'll skip that to quote two paragraphs from the middle of the article):
"To distill a frequenly cited example: Consider the two classes Square and Rectangle each have virtual functions for setting the height and width. The Square cannot correctly inherit from the Rectangle because the code that uses a modifiable rectangle will assume that SetWidth does not change the height (whether a Rectangle explicitly documents that contract or not), wheras Square::SetWidth cannot preserve that contract and it's own invariant at the same time. But Rectangle cannot correctly inherit from Square either, if clients assume that a Square's area is width squared, or some other property that doesn't hold for Rectangle.
The "is-a" relationship is misunderstood when people use it to draw irrelevant real-world analogies: A Square "is-a" Rectangle (mathematically) but a Square is not a Rectangle behaviorally. Consequently, instead of "is-a", we prefer to say, "works like a" (or, if you prefer, "useable as") to make the description less prone to misunderstanding."
modified 11-Nov-13 20:31pm.
|
|
|
|
|
Well, I can see that their text is pretty clear.
And when I said a related Rectangle and Square, I was talking about a Rectangle and a Square classes that could be put in the same "list", as you previously suggested to use some common base class.
What I really meant is this: When we use inheritance (in this case, I am talking about inheriting from another base class, not a Square based on a Rectangle or vice-versa, so both based on something like a "Geometry2d"), we usually try to reuse something, at least a virtual method. In this case, it would make sense to create a list of, for example, a Square only, a Rectangle only or of the base class.
But note that in my article I am only talking about width and height. I am not actually talking about total area (which may really be part of a Geometry2d class, but in such a case we can also have a Triangle as a Geometry2d when for some reason we may only want rectangular forms) and also, for all effects except casting, a Rectangle with the same value for Width and Height is a square. That is, except for memory consuption, having a dedicated Square class will buy you nothing.
And as discussed in the article (and even on my posts), by having them as separated classes will not allow you to pass a Square when a Rectangle is expected (you will need to create a Rectangle based on that Square) and you could not pass a Rectangle with equally sized Width and Height when a Square is expected.
Finally, about the inverted situation the authors talk, where a Rectangle inherits from a Square, I can say that any client that assumes the exact result for a given property can never use inheritance, as inheritance is all about changing some behavior and, in fact, it will work if done properly. For example:
Square: Size = 2. Area = 4.
But, in fact, it is a Rectangle: Width 4, Height 1.
In this situation, the Size is not directly reused by the Rectangle as one of its two properties. It is "calculated" by its own properties. And, if the Size is virtual, we could even take this Rectangle (4, 1) and change its size to 4 (it is originally 2) recalculate its Width and Height by its proportion. That is, a Square of 4*4 = 16. The new Rectangle will have an area of 16, but its Width will be 4 times its Height.
But in fact discussing all the possibilities to make the Rectangle inherit from a Square can also span another entire article as the basic conclusion will be the same they had.
In fact, I will stay with a Rectangle class only, as a Square is nothing than a Rectangle with the same Width and Height and, if needed, I will check if both the Width and Height are equal. In fact, I will probably also make the Rectangle class immutable (or even a struct in .NET).
And also note that I never said that a Square should inherit from a Rectangle, I said that if you really want to do it, only to prove you can, that I will show the alternatives. And an alternative with immutable objects and a factory, where a Rectangle.Create(10, 10) will create a Square will have all the compile time guarantees (methods that expect a Square will not accept rectangles) yet, through casting, you will be able to see that Rectangle(10, 10) as a Square as the factory will, in fact, create a Square that inherits from a Rectangle without loosing any trait. So, if I stick with the immutable solution, I can create a Square that inherits from a rectangle, as after created I will not be able to set its Width and Height and so, I will not break any guarantee made by the Square class (though I will continue to use more memory than needed).
|
|
|
|
|
So, summing it up, I can say that:
An immutable rectangle with equally sized Width and Height is a Square, with all the functional traits of a Square. It may use more memory, but I don't really think it justifies creating a new class only for this.
I understand that the authors of the book say that you should not inherit a Square from a Rectangle, but I don't have the book to see if they discuss immutability or not as even with inheritance immutable Squares can inherit from Rectangle without any problem.
I also don't see that they are telling that we should have the two as separate classes. It looks more like "if you really want to have both, they should not inherit from each other". I really believe the authors will not see a problem with a Rectangle class only.
So, still don't agree that my solution is "sub-optimal". To me, having mutable squares and rectangles is the most sub-optimal solution as we will be able to have a Rectangle that are squares by their traits without working where Squares are expected (and vice-versa), independently if they have a common ancestor (like Geometry2d) or not.
If you still believe my solution is sub-optimal, then you can vote 1 for the article as I will not discuss this anymore.
|
|
|
|
|
"If you still believe my solution is sub-optimal, then you can vote 1 for the article as I will not discuss this anymore."
Oh no! I would not vote your article down for an issue like this, whether I agreed or disagreed. It's a good article. I just want to provide information.
I just voted your article a 5.
You clearly understand the various complex issue regarding inheritance. Anything other issues are really nitpicking.
|
|
|
|
|
So, I thank you for your vote.
And I hope that you understand that the presented solutions aren't really "sub-optimal", as I am talking about points that "normally" are problematic, but I am explaining them and also showing solutions that don't violate the Liskov Substitution principle, be it the immutable solution (as it is not allowed to change values at all, so it can't break a condition that's necessary to a Square) or by simply not having a second class at all, as the Rectangle is able to exist as a Square without the wrong behavior, even if it uses more memory.
|
|
|
|
|
Bill_Hallahan wrote: That perspective is that the "is a" rule you cited is not considered a good rule
to use for inheritance.
Isn't that exactly what Barbara made a principe of? The LSP? A subtype must always fulfill the "is a" logic regarding its basetype implicit.
yetibrain
|
|
|
|
|
I posted what is below earlier. Herb Sutter and Andrei Alexandrescu state it best:
From the book "C++ Coding Standards - 101 Rules, Guidelines, and Best Practices" by Herb Sutter and Andrei Alexandrescu, Item 37 "Public Inheritance is substitutability. Inherit not to reuse, but to be reused", pages 64-65. (The authors explain what the title means - I'll skip that to quote two paragraphs from the middle of the article):
"To distill a frequenly cited example: Consider the two classes Square and Rectangle each have virtual functions for setting the height and width. The Square cannot correctly inherit from the Rectangle because the code that uses a modifiable rectangle will assume that SetWidth does not change the height (whether a Rectangle explicitly documents that contract or not), wheras Square::SetWidth cannot preserve that contract and it's own invariant at the same time. But Rectangle cannot correctly inherit from Square either, if clients assume that a Square's area is width squared, or some other property that doesn't hold for Rectangle.
The "is-a" relationship is misunderstood when people use it to draw irrelevant real-world analogies: A Square "is-a" Rectangle (mathematically) but a Square is not a Rectangle behaviorally. Consequently, instead of "is-a", we prefer to say, "works like a" (or, if you prefer, "useable as") to make the description less prone to misunderstanding."
|
|
|
|
|
This is a great inspiring article. I am pretty much pleased with your good work. You put really very helpful information. Keep it up once again.
|
|
|
|
|
|
|
Thanks for the vote and for the comment on the "Square is not a Rectangle" discussion.
|
|
|
|
|
|
nice work
|
|
|
|
|
|
A clearly written and interesting article with a different take on the LSP square/rectangle issue.
Just because the code works, it doesn't mean that it is good code.
|
|
|
|
|
|
Paulo has such a strong ability to think outside the box, he brings an enlightened vision on many things, often too readily accepted without questioning.
|
|
|
|
|
|
My vote of 5 because the article is very interesting. It inspired me a lot.
Well, OO offers inheritance but possibly forgot to consider type morphing!? If we allow for a rectangle that width and height can be altered, once width and height will be set to the same value, it will become a square (while it is still a rectangle from the view of inheritance [edited after i have read Gustav's posting]).
Considering the composition versus inheritance discussion, an apple can become a lemon at runtime if we want so.
Considering type morphing a seat can morph to a tree.
Consider embryo->baby->boy->man, it's the "age" property that changes the type of the instance. Now, "is a" man a boy? "is a" boy a baby? "is a" baby an embryo? True or false?
Now, "was" a man a boy? "was" a boy a baby? "was" a baby an embryo?
yetibrain
modified 27-May-13 9:24am.
|
|
|
|
|
|
Paulo Zemek wrote: And about the "type" morphing, well, it is not the type that is morphing. The
type in the embryo->baby->boy->man is Human. What change is a property
(that's why I consider the IsSquare as a property as the best solution).
Consider a square to become just a rectangle by changing just the values of the properties, in this case the type changes. As far as i know types are always fixed, as well as the type hierarchies that depend on the also fixed inheritance. So if a type "square" and it's instance "MySquare" finishes to be a square, how would the type change to another type name not named "square"? Imagine your "IsSquare()" method returns false and at the same time your method "GetType()" returns still "square" and the name of your instance would still be "MySquare", wouldn't this be a mismatch that leads to misunderstanding and inconsistancy concerning classification? Imagine an object "MyLemon" of type "Lemon" that has a method called "IsLemon()". If the type already classifies the object as a lemon, why does the lemon class need a IsLemon() method?
I agree with you if you don't use inheritance and just use the base class as the type, in this case "rectangle". The values of the properties width and height would decide what specific type the object really is. Well composition would allow to host a child object based on width and height. By changing properties the child object could be deleted or created (in this case the square).
yetibrain
|
|
|
|
|