Indeed, I think inheritance gets a really bad rap in Ruby b/c Ruby's
base classes and inheritance system are so poorly designed to handle
it.
I would not subscribe to that. Although Bertrand Meyer is a big fan of
implementation inheritance I am not yet convinced that it is such a good
idea so often. Of course, there are always uses for any technique present,
but I find the "is a" relationship interpretation of inheritance so clear
and obvious that I somehow feel bad about polluting inheritance with other
uses. I cannot really put forward a more concrete argument or even back
this up by some hard (business) numbers, but a world in which "A inherits B"
<=> "A is a B" is so much simpler. And in Ruby, whenever you need
implementation inheritance you can use a mixin module. Enumerable is a very
good example for that.
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, both
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 = width, or is a rectangle a
square without the constraint of height = 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.
Whatever 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 *= 2;
x.height *= 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 == 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 = width
@height = height
end
def outline
2 * ( width + height )
end
end
class Square < Rectangle
def initialize(x)
@width = 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 = width
@height = height
self
end
end
class Square
def resize(width, height)
raise ArgumentError if width < 0 || height < 0 || width != height
@width = 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.
The other is where the inheriting object has to learn a new skill at run
time. A teachable foo is not a foo.
The other day I was talking to my Father, a Cobol programmer from back
in the day, and he was telling me that when he retired, OOP was just
starting to get hyped. He was quite interested in it at the time and
then rattled off some of the advantages he remembered it was to bring
to the field. Inheritance for code reuse was high on the list and
Well, there are other forms of code reuse - even in procedural languages.
It's not that you _need_ inheritance to have code reuse. I would even go
as far as to claim that it is more difficult to implement classes intended
solely for implementation inheritance than classes which implement a
particular real world concept and which are only inherited if "is a" is
needed. I say this because a class intended for implementation inheritance
needs other criteria for including functionality than a class which
implements a particular concept.
related to that, one point eally struck me -- instead of versioning
code as we have become accustomed, the idea was to subclass the
original class and implement changes in the subclass. Using some sort
of class name referencing scheme you could use any version.
That does not sound like a, um, proper application of inheritance to me.
It might be useful to do it that way in some rare conditions but I would
not promote this as a big feature of inheritance.
IIRC, (I started back in the day also), it was promoted as a way to achieve
data migration. You altered the schema, and altered the class to match, but
only converted the data if it was edited. It was helpful in situations where
just *linking* the OLTP took 36 hours, and updating all the data would have
meant the service was down for days - too expensive to contemplate.
Wow, that's an amazing roundtrip time! I guess that situation has
changed dramatically nowadays (unless, that is, you have to compile
and link SAP maybe :-)).
Kind regards
robert
···
On Tue, Nov 2, 2010 at 11:44 PM, Ian Hobson <ian.hobson@ntlworld.com> wrote:
On 02/11/2010 17:40, Robert Klemme wrote:
On 02.11.2010 17:37, Intransition wrote:
On Nov 2, 5:31 am, Robert Klemme<shortcut...@googlemail.com> wrote:
--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/