Re: Composition vs. inheritance
Todd wrote:
On Apr 23, 1:24 am, Ulrich Eckhardt <eckha...@satorlaser.com> wrote:
I can give you a few examples when it is not appropriate to inherit. One
of the things already started the discussion mentioned above, like e.g.
deriving a square class from a rectangle class. If you do that, you are
simply violating the Liskow(sp?) Substitution Principle, because
inherently expected behaviour of a square is incompatible to that of a
rectangle. A similar example is the relation between circle and ellipse.
In both cases, you have behaviour of one type (being able to set
width/height separately) which can't be combined with behaviour of the
other type (width and height are exactly the same).
Uli,
The shape example that you give as to when it is _not_ appropriate to
use inheritance is exactly the example that was used in the courses I
have had in C++ and Java to show proper use of inheritance. So, if I
interpret this example correctly, as opposed to something of the sort:
abstract public class Rectangle
{
public Rectangle( double height, double width )
{
setHeight( height );
setWidth( width );
}
public void setHeight( double height )
{
this.height = height;
}
public void setWidth( double width )
{
this.width = width;
}
public double getHeight()
{
return height;
}
public double getWidth()
{
return width;
}
private double width;
private double height;
}
Okay. Let's sum up what I would expect if I only knew the interface of this
class:
1. Being able to read back values:
rect.setWidth(2);
if(rect.getWidth() != 2)
error();
2. Independently setting values:
rect.setWidth(2);
rect.setHeight(42);
if(rect.getWidth() != 2)
error();
public class Square extends Rectangle
{
public Square( double side )
{
super( side, side );
}
public void setHeight( double height )
{
super.setHeight( height );
super.setWidth( height );
}
public void setWidth( double width )
{
super.setWidth( height );
super.setHeight( height );
}
}
Yes. Here. the confusing part is that the second example above doesn't work
any more. Further, the reason for that is non-obvious: I have a rectangle,
set its width and height and they are then not as I set them!
which changes the behavior of setHeight and setWidth, you would favor
a Square somewhat like:
public class Square
{
public Square( double side )
{
square = new Rectangle( side, side );
}
public void setSide( double side )
{
square.setHeight( side );
square.setWidth( side );
}
public double getSide()
{
return square.getHeight();
}
private Rectangle square = null;
}
Well, I'm not sure if I would use a class Rectangle in order to implement
the square but in general, I agree with the behaviour and interface.
wherein the behavior of the Rectangle is hidden from user's of the
Square and methods with little/poor meaning are unavailable. (So in
this case, the Square is composed of a Rectangle, right?) This makes
sense since the behavior of the two items is inherently different,
however, I am still uncertain as to when to compose vs. extend when
the behavior is inherently the same.
For example, what if I wanted to ensure that MyRectangle always has a
longer width than height? The methods in an extended class will be
nearly identical to those of a composed class (with super replaced
with the composed object name):
public class MyRectangle extends Rectangle
{
public MyRectangle( double width, double height )
{ [...] }
public void setHeight( double height )
{
if( super.getWidth() < height )
{
super.setHeight( super.getWidth() );
super.setWidth( height );
}
else
{
super.setHeight( height );
}
}
public void setWidth( double width)
{
if( width < super.getHeight() )
{
super.setWidth( super.getHeight() );
super.setHeight( width );
}
else
{
super.setWidth( width );
}
}
}
Again, the same confusing behaviour ensues. It's hard to envision the case
where you would need this, but just like with the case of a square, I would
consider creating a separate interface. Otherwise, I would consider only
using class Rectangle and wherever some code places additional restraints
on it either fix 'invalid' rects or throw an error.
public class MyRectangle
{ [ similar implementation but using aggregation ] }
I don't see how one relies less on the implementation of the Rectangle
class less than the other, since they both access the methods of the
Rectangle class.
The important point is that one version claims to be a Rectangle while the
other doesn't. Even though the aggregate version uses a Rectangle, a use
will only look at the public interface of that class in order to understand
how to use it. When deriving from Rectangle, the user will look at
MyRectangle's interface and its baseclass' or maybe only at the baseclass'
interface (because they might only have a reference to a Rectangle).
Obviously, I am missing something fundamental, so I apologize if I
seem to be going in circles (no pun intended).
I don't think I can give you a final answer to this. Much of this requires
feeling and experience in order to determine which one is right. I wouldn't
even call something like this 'right', it is just a question of what is
better for the program, ease of understanding, extending, maintenance,
testability, scalability etc. It's not black and white, it's grey.
FYI, I didn't compile any of these examples, so please don't take them
as an SCCE.
No problem. ;)
Uli
--
Sator Laser GmbH
Gesch?ftsfXhrer: Michael W?hrmann, Amtsgericht Hamburg HR B62 932