There are, IIRC, two places where is a, is not well implemented by
inheritance.
One is the classic square and rectangle problem. In a graphics library, b= oth
square and rectangle are graphic objects that can be drawn on the screen,
moved and resized etc. But what is the relationship between square and
rectangle? Is a square a rectangle with height =3D width, or is a rectang= le a
square without the constraint of height =3D width, or does is-a not apply= at
all?
The rule is pretty easy: state of subclasses can be separated in two groups=
:
1. state present in superclass
2. everything not in 1
Now the rule to maintain "is a" relationship is simple: the set of
allowed states in group 1 must be a subset of the set of allowed
states in the superclass (not a proper subset, so both may be
identical). Allowed state in group 2 is totally governed by the
subclass's invariant.
From this it follows immediately that, if inheritance is used here,
Square can only be a subclass of Rectangle and not the other way
round.
=A0Whatever the author of the package chooses the users have to know if
they have a square or a rectangle, or some very reasonable code sequences
will fail to do what is expected.
x.width *=3D 2;
x.height *=3D 2;
If x is a rectangle this doubles both dimensions.
if x is a square, this will quadruple the dimensions, or fail.
A square _is a_ rectangle where width =3D=3D height. That's the
definition in math and that's how I would implement it usually. The
simplest version would be
class Rectangle
attr_reader :width, :height
def initialize(width, height)
@width =3D width
@height =3D height
end
def outline
2 * ( width + height )
end
end
class Square < Rectangle
def initialize(x)
@width =3D x
end
def height; width; end
end
Note that in Ruby, different than in other programming languages,
@height is not wasted in a Square because it is never allocated. In
other languages you would simply ensure that both members had the same
value all the time in class Square.
Now if you need mutable squares and rectangles you need to devise a
method to manipulate Rectangle which can be used by subclasses as
well, e.g.
class Rectangle
def resize(width, height)
raise ArgumentError if width < 0 || height < 0
@width =3D width
@height =3D height
self
end
end
class Square
def resize(width, height)
raise ArgumentError if width < 0 || height < 0 || width !=3D height
@width =3D width
self
end
end
In a more realistic example you would probably make Rectangle inherit
Polygon and have completely different code for outline calculation and
manipulation. Then also there would be a generalized storage of side
lenghts and angles which would cope with arbitrary many sides thus not
wasting space for a member that is not used.