|
I totally agree with you on the fact that there is no point in separating squares and rectangles into two classes. A square is only a special instance of a rectangle whose width and height are equal. Just as a right triangle, an isosceles triangle and an equilateral triangle are triangles before all, and saying if one is right, or equilateral, etc..., just amounts to watch for its properties. Thus, the IsEquilateral, IsIsosceles, etc... boolean properties.
A really interesting and quite instructive article. Thank you for this one, I really enjoyed its reading.
I never finish anyth
|
|
|
|
|
I am really glad you liked.
|
|
|
|
|
Just talking...
I didn't know about Liskov Substitution but I find your square/rectangle a bit twisted.
I think that the nature of the design tell if geometry rules drive the implementation or not (inheritance or not and how). Because you could also inherits from rectangle and force the other dependent property (sibling width or height) to be the same as the one that it is currently set (only another potential way of doing think in another context).
About WPF attached property. I use them each day but I HATE them. They breaks good rules of programming. The layout could have been done differently without those attached property where they break the FIRST rule of good software design: High cohesion and low coupling. Microsoft did another mistake there, I think.
Thanks,
Eric
|
|
|
|
|
Thanks for the vote.
About the WPF properties... I actually think that part of the idea is great. Yet, I don't like how verbose it is to create a single property.
|
|
|
|
|
Well written, and i think its almost essential that these types of explanations end with a real world example. To often the example is so abstract that you can't actually 'get' what's being explained.
|
|
|
|
|
Thank you very much for the vote and the comment.
|
|
|
|
|
|
|
You wrote:
"My personal solution to this problem is: There should be only a Rectangle type. A Square is simply an observation of the properties of such Rectangle. So, a property like IsSquare will solve the problems if we compare it to how it works in Geometry.
That is: You can create a Rectangle of Width 100 and Height 200. The IsSquare will return false. Then, you can change Width to be 200 too. If you get the value of IsSquare, it will be true. Great!"
This doesn't solve the problem. If IsSquare indicates that the rectangle isn't a square, and you need a square, it's not clear whether the square should be 100 by 100, or 200 by 200.
Suppose I want a Square object that acts like a square. Since the length and width of a square must be the same value, it would seem that setting either the width or height should automatically set the other one. Of course, that solution creates other problems.
The book, "C++ Coding Standards: 101 Rules, Guidelines, and Best Practices" by Herb Sutter and Andrei Alexandrescu, gives this exact problem of a rectangle and a square to demonstrate the issue with the "is a" rule used to justify using inheritance. The authors suggest that the problem is the "is a" rule is not correct. Instead, the rule should be, "has the same interface".
A rectangle must have two independent methods. A square requires one method. So, they don't share the same interface and so the latter should not be derived from the former.
The book goes into more detail on this issue.
Update: The book actually states the guideline as ""works like a", not "has (shares) the same interface", although the guideline that I inferred from the article is probably okay too.
modified 9-Nov-13 12:26pm.
|
|
|
|
|
You apparently didn't read the entire article or you are simply using an invalid argument.
You say: "If you need a square, it's not clear whether the square should be 100 by 100, or 200 by 200."
Well, with a mutable Square that will be always impossible to tell. After all the Square will only guarantee that both width and height have the same value, not if it is 100x100 or 200x200. To check that, you will need to use normal "if" conditions and so, you will be able to use a Rectangle and check for those conditions.
So, this also means that if you can't have an object that starts with a particular size and then changes to another size, you will never be able to accept mutable objects and I presented the solution to this kind of problem: Use immutable objects. In such a situation you can use inheritance without any problem.
But I am really considering the "normal" and not hyper-complex use case: Allow a Rectangle to be mutable, and put an IsSquare property in it. If you then pass such a Rectangle (or Square) to a method, such method can verify if it is receiving a square or not and, if needed (for example, if creating a new object) it can copy the appropriate values. But if it only uses the object to return a value and then forget it (that is, more like functional programming) then we should not care about future changes to the "Square/Rectangle".
Edit: Oh, I think I understood your problem... if I pass a Rectangle of 100x200 where a Square is required, an exception should be thrown. So there's no problem about which value to use. Trying to use one of the values in both places means you want to cause problems. It is as bugged as a Width property that also sets its Height value. And I continued the article with this paragraph:
"Of course we can validate that at run-time (the same way we can validate if any parameter is null at run-time), but what if we want to solve the problem of a Square and a Rectangle using real types?".
So, if you read that part you will see that:
A) I say validate, the same way we can validate for null values. I don't say to accept some values. What we usually do with null values? We throw an ArgumentNullException.
B) I let it clear we will lose the compile time validation if you really need a Square.
C) I say that if you still want to see a solution using square and rectangles, that I will present them... which in fact leads us to the immutable solution.
So I think there's isn't a problem in my article. The problem is that you got a paragraph out of context to complain.
|
|
|
|
|
You wrote:
"You apparently didn't read the entire article or you are simply using an invalid argument."
First, I read the entire article and I believe I understood all of it.
Second, it's not my argument, it's Herb Sutter's and Andrei Alexandrescu's argument. They are both Phds in the field and recognized as authorities. That, of course, doesn't make them infallible, but it does make it worthwhile to pay attention to their opinions.
They claim, and after considerable thought, I agree, that the "is a" rule can lead to sub-optimal designs involving inheritance.
You wrote:
'"You say: "If you need a square, it's not clear whether the square should be 100 by 100, or 200 by 200."
Well, with a mutable Square that will be always impossible to tell. After all the Square will only guarantee that both width and height have the same value, not if it is 100x100 or 200x200. To check that, you will need to use normal "if" conditions and so, you will be able to use a Rectangle and check for those conditions."'
You wrote (in the article):
"That is: You can create a Rectangle of Width 100 and Height 200. The IsSquare will return false. Then, you can change Width to be 200 too. If you get the value of IsSquare, it will be true. Great!"
My initial comment was responding to that quote from the article, where, even in context, it's not clear there is a reason to choose that the square should be 200 by 200 as opposed to 100 by 100. I see now you write it cannot be determined which is correct. In any event, this has nothing at all to do with my main points, which I mention below. (I see you updated this in your last message where I believe you acknowledge what I meant).
One of my main points is, that if making a class called Square, it should never derive from Rectangle.
It should be impossible to make an object called a Square that isn't a square. Because the SetWidth and SetHeight methods of the rectangle class are accessible to the Square interface, there are issues with deriving a Square class from a Rectangle class, some of which your article mentions, and there is no solution with such derivation that is as good as not deriving the Square class from the Rectangle class.
The SetWidth and SetHeight methods no sense in a Square class because a Square class shouldn't be able to individually set the width or height without changing the other. A Square class shouldn't have a SetWidth and SetHeight method. A Square class should have a SetSide method that takes a single value that applies to both dimensions.
You wrote:
public class Square:
Rectangle
{
public override bool TrySetWidthAndHeight(int width, int height)
{
if (width != height)
return false;
Width = width;
Height = height;
return true;
}
One of my main points is that, for example, just because a square "is a" rectangle, doesn't mean that a square should be derived from a Rectangle. This is the point the authors of the book that I cited also make.
Note, I realize the code can be made to work. My point is that storing two independent quantities to represent a single value has no advantages, has detriments, and complicates the design unnecessarily.
The book I cited goes into more detail on why the "has the same interface" relationship to determine whether to use inheritance often leads to far better designs than using "is a", and again, the example they used is exactly the same example you cite, they even call the classes Rectangle and Square. Their conclusion is that a Square should not be derived from a Rectangle.
You wrote:
"So I think there's isn't a problem in my article. The problem is that you got a paragraph out of context to complain."
I am not complaining at all. I want to give you another perspective regarding what I have learned from a very good book that I have read by two notable and well-known authors and experts on both the C++ language and object-oriented design. Both men have authored multiple books on these subjects.
That perspective is that the "is a" rule you cited is not considered a good rule to use for inheritance.
The authors make the case that using the "is a" rule to justify this inheritance is a mistake, and the "is a" rule should be abandoned, and replaced with "has the same interface" (although they mean, "shares the same interface," since obviously methods might be added to a derived class that are not in the base class).
modified 7-Nov-13 20:12pm.
|
|
|
|
|
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.
But let's start from the scratch: I only used the Rectangle and Square example because it is one used very often. But in most situations people add a new base class, which still has an independent Width and Height and think they solved the problem (some of them even make the Rectangle and Square to be parallel classes, not one the base of the other, yet their base class still has the problem).
And then I finally talk about possible solutions. To me, the most simple solution is reduced to a single class with an extra property. I myself told that such a solution may not give the same compile-time guarantees that having separate classes may give... and then I discussed possible solutions. I even put a big "Note" at the end of my examples:
"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."
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". And I also explained that the "is a" by itself is a problem about the expression, after all such expression is used to say "a square is a rectangle" and is used to talk about inheritance, when it is not necessarily an inheritance "is a" expression... it is a simple expression.
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".
And the example of 100x200 that later becomes 200x200... I am not saying the class will tell which one is the best to do. I am simply talking about a possible (and stupid) use case: Someone creates an object of 100x200. That someone later decides to change some property, making it 200x200. What's the value of IsSquare?
That person could decide to change both Width and Height to 300. I don't care. I am simply telling that by using a property, not a class, we will have the possibility to test at any moment. I am not saying: If it is 200x100, then change it to 200x200. I am saying that you can test a 100x200, 150x10 or anything different and the IsSquare will return false. If you modify the properties and they become equal (I am not saying that you should choose one to change), then the IsSquare will return true.
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.
See: "Is a" is completely different from inheritance. And I prefer to avoid two classes if I don't see a real gain (the only real gain I can see is that you will use less memory to store a square [one property] instead of a rectangle... but I rarely see real programming situations that requires us to save that amount of space and, if we do, we can simple store an "int" representing the "size".
------------
After conclusion: I said I was concluding... but there's a secondary conclusion: What if you want to create a single list to put all your objects? Some will be rectangles width different Width and Height (IsSquare = false), some will not (so IsSquare = true).
With a solution that has a single class I can use any rectangle with IsSquare = true in a method that requires a Square. But if I have separate classes, I will need to create a new Square to be able to use it on such a method, considering at the first moment I only created rectangles, even if some of those are squares too.
But if you insist in creating Rectangles, Squares (and possibly a base class that doesn't contain any rectangle or square specific methods) you will either:
* Have a list of rectangles, which will not accept squares (considering the Square type, as it can accept rectangles with square sizes);
* Have a list of that base class, that may accept any;
And to call methods that receive Squares you need to do checked casts. Worse, to call methods that require rectangles you will need to do casts to rectangles or you will need to cast to Squares and then create an adapter, which definitely goes against the purpose of inheritance and code reuse. But be free to do it if you want.
modified 7-Nov-13 20:22pm.
|
|
|
|
|
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.
|
|
|
|
|
|