From: Robert Klemme Date: 2010-11-03T17:20:58+09:00 Subject: Re: the dark side of inherited methods On Tue, Nov 2, 2010 at 11:44 PM, Ian Hobson 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  wrote: >>> >>> 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 -- remember.guy do |as, often| as.you_can - without end http://blog.rubybestpractices.com/