raganwald
Tuesday, March 11, 2008
  IS-A IS-A HAS-A
All of the pain caused by inheritance can be traced back to the fact that inheritance forces ‘is-a’ rather than ‘has-a’ relationships. If class R2Unit extends Droid, then a R2Unit is-a Droid. If class Jedi contains an instance variable of type Lightsabre, then a Jedi has-a Lightsabre.

The difference between is-a and has-a relationships is well known and a fundamental part of OOAD, but what is less well known is that almost every is-a relationship would be better off re-articulated as a has-a relationship.
—Bernie Sumption, Inheritance is evil, and must be destroyed

I’m in the middle of dealing with some ActiveRecord, um, ‘features,’ so I don’t have time to write an interesting, thoughtful essay. Nor do I have time to write an overly long and meandering essay as Raganwald readers have come to expect. So instead, let’s pretend there’re six or seven closely typewritten pages of self-indulgent bloggery leading up to…

IS-A IS-A HAS-A

When classical OO languages (including Ruby and Java) implement inheritance, what actually happens is this: First, the subclass acquires the interface of the superclass (latently typed languages like Ruby don’t do this explicitly, but it is still true). Second, under the hood where you can’t see it, the compiler makes room for a reference to the superclass and arranges for methods that the subclass does not implement to be delegated to the superclass.

In other words, whenever you declare that Child IS-A Person, the compiler writes “Child HAS-A Person” and “Child BEHAVES-LIKE-A Person” and “Child DELEGATES-TO Person” for you.

The right way to look at single inheritance is therefore that it is a specialization of composition. Like all specializations, it does less. It is more specific. And therefore, when it is exactly what you mean to accomplish, it is more useful: you need fewer lines of code to express it, the compiler optimizes the relationship for you, and since it is a very well-known idiom, it expresses your intent clearly to other programmers.

On the other hand, when single inheritance is not exactly what you mean to express, you have two choices: you can greenspun some more general feature out of it, adding accidental complexity to your code and making it less readable, or you can use the more general purpose, flexible tool: composition.

Like all Golden Hammers, seeing every OO problem as a single inheritance nail leads to trouble. Single inheritance is probably Turing Equivalent: given enough determination, you can probably write any arbitrary program if you are prepared to twist your code into knots as it bows down and sacrifices readability to the inheritance demon.

However, breaking free of its grip is simple: you need only see inheritance as a tool like any other tool: it is not superior to the others at your disposal. It is not the goal. A nice pyramid of classes and subclasses is not your objective when you set out to design a program. When you see it as just another chapter in the book, you see when to use it and when to set it aside.

And in fact, if you see it as a particularly thin chapter in the book, you may be surprised how much easier it is to work with OO programs. And if you see it as a couple of pages in the chapter on composition and delegation… you will be on the road to understanding.
 

Comments on “IS-A IS-A HAS-A:
I really like your definition of inheritance as it allows a much easier explanation of why protected attributes should not be used. Seen from the subclass they really are public attributes of the superclass considered as a has-a
 
> Single inheritance is probably Turing Equivalent

garr! You set off all my math alarms!

Is there any meaningful way in which this is anything but false? I can see no definition of "single inheritance" as equivalent to a computing machine in any way.
 
For more detail on why this post is correct and why inheritance should only be used when appropriate, consult section 6.3 of Code Complete 2/e.
 
> So instead, let’s pretend there’s six or seven closely typewritten pages of self-indulgent bloggery leading up to…

This made me laugh :)
 
I often think of single inheritance as a rapid prototyping tool for class design. Even when I know that I'll eventually want composition (maybe so that I can stick in States and Strategies and Decorate them), it's just so darn easy to type and think about single inheritance. Then I can quickly get my feet wet in the problem domain, and later refactor my classes into composition and delegation patterns, once I've got a better feel for their contracts with each other.
 
IS-A IS-NOT HAS-A.

What the compiler does is purely an implementation detail -- sometimes useful for understanding the semantics, sometimes plain misleading. In a has-a relation the 'subclass' cannot overload the 'superclass' methods to change the behaviour of the superclass; it can't delegate back. (And those are the method that may best be 'protected'.)

Whether modifying the base class behaviour by overriding its methods is a good idea? At least it's usually a documentation nightmare as the base class needs to make very specific what can or must not be done. java.lang.Classloader is such a skimpy case. There are also namespace issues; call your java method wait(), and funny things may happen.

Also unfortunately the modern languages don't have a mechanisms to make the distinction between HAS-A and KNOWS-A; between a 1:1 relation between sub and super and a 1:n relation to a shared object. C(++) makes that possible by the distinction between objects as members and references/pointers to other objects; including the automatic initialization. (Not that I particularly like C++, this is just one of its better corners.)

Inheritance is for reusing implementation; for just implementing the same interface we (the static type gang) nowadays have, ahem, interfaces. In C++ both run under the same heading, and make multiple inheritance much more a need.

HAS-A subobjects sometimes need to callback to the having object; think timers for example. In C++ this is much easier by providing a timer class having a abstract virtual method for the callback than to call a method on another object. That is why there are private base classes there.

And for the last tangent... C++ actually has duck typing. In templates.
 
Excellent title. I knew exactly what you were going to write, after reading the title.
 
Thanks for this Bernie - I'm just getting to grips with the surprisingly (?) frequent usefulness of composition over inheritance, and your little definition really helps me get things straightened out in my mind.

And thanks for not making me read the other 6-7 pages ;-)
 
This conversation surfaced with JavaPosse a while back, and I've been fighting with it for years.

http://enfranchisedmind.com/blog/2005/10/28/a-java-gotcha/
http://enfranchisedmind.com/blog/2007/10/29/java-posse-equals-inheritance-and-the-liskov-substitution-principle/

(Notably, I even cited one of your posts in one of those.)

The problem with considering is-a to be a special form of has-a is that it's only accurate from the internal world of the class: the class can consider its is-a to mean has-a-and-automagically-delegates-to-a, but the external world has an additional expectation, which is the whole Liskov Substitution Principle thing.

Of course, you're coming from the world of dynamic typing, which means the external world doesn't really care about your inheritance hierarchy -- as long as you answer my calls with something that sorta kinda makes sense to me and don't throw exceptions, you're good to go, and problems like equals-under-inheritance are delegated from the class system to the user to handle.
 
Of course, you're coming from the world of dynamic typing, which means the external world doesn't really care about your inheritance hierarchy -- as long as you answer my calls with something that sorta kinda makes sense to me and don't throw exceptions, you're good to go, and problems like equals-under-inheritance are delegated from the class system to the user to handle.

A common, and entirely understandable misunderstanding. Both latent and the explicit typing systems take the same approach, namely that there are two issues: an interface, and an implementation.

In C++, these can be entirely separate: abstract base classes declare interfaces without implementation, and private/protected inheritance declare implementation without interface.

Your statement above really talks about the fact that some languages do not have an easy way to inherit implementation without interface. This is why in Java--for example--the external world MUST care about your inheritance hierarchy.

If we were comparing C++ to Ruby, to name two languages, it would be easier to disentangle the issues cleanly.
 
Technically, you're correct. But, in statically typed languages, there's an implication that if you inherit from another class, you're a specialization of that class (for some handwave definition of "specialization). If your behavior screws up, it's assumed to be a fault of the library, because you've violated this development convention.

In implicitly typed languages, since I don't really know anything about the class information, I can't make that (possibly erroneous) assumption. And so I (as the user of the code) need to check to make sure your code behaves the way I expect it to behave, not only at development time but as a runtime piece of safety.
 
(for some handwave definition of "specialization)

This is a post I have on my back burner. I prefer the "Strict" interpretation of the Liskov Principle, meaning essentially that if I have a class C I can write a collection of unit tests for that class. Now if I specialize it to create D < C (Ruby nomenclature) or D extends C (Java nomenclature), I must be able to run all of my C tests on D objects and they all pass.

This implies forcefully not to override behaviour of concrete classes. Extend it, but not override it.

So if I override a method, it is only to add new bahaviour, such as handling new options, not to change existing behaviour.

This is not the only or best way to do things, but it lets me sleep at night whether I'm using Java, Smalltalk, or Ruby.
 
It's funny, just the other day I was thinking about how the Ruby community has started to take this advice a little too far.

Inheritance hierarchies in the Ruby code I see these days are mostly shallow to nonexistant, which is generally a Good Thing. But I've been running across cases where it seems like it didn't even occur to the author that he or she could extend a class by inheriting and overriding a method or two; instead, they chose to re-open the class and overwrite the offending methods.

Not that inheritance is necessarily the best form of class extension for all cases; but it's usually an improvement on method overwriting.
 
@avdi

http://wiki.rubyonrails.org/rails/pages/HowtoConfigureLogging

From "Why doesn’t Logger behave the way the Ruby library docs say it should under rails?"

Look at the source to what Rails 1.2 does to mung the standard Logger object to do what it wants. Can you see anything wrong with this picture? If Rails wants a Logger object that behaves this way, it should subclass Logger, or inject its own Logger::Formatter into the picture rather than this mess.

What this accomplishes is utterly evil. If you’re adding things to a rails app that expect Logger to behave as documented, you’ll configure your logging date formats all day long and never have any impact. Worse, the code is flagged :nodoc:, so after screwing around with expected behavior, we’re left with no indication as to why a Logger instance created from the Rails console will behave different than a Logger created from IRB.

Mixins are a great feature of Ruby, but a mixin that irrevocably alters a core library class just to avoid the minor pain of creating a proper subclass where it it obviously necessary is just plain wrong, no matter how $!#^$% pragmatic you’re claiming to be.

 
I thought we were long past this discussion. "Composition over Inheritance where possible" - isn't that part of the basics?
 
"Composition over Inheritance where possible"

Really? What does "possible" mean in the context of a Turing-equivalent language? I could argue that it is always possible to use composition and eschew inheritance.

Inheritance is a first-class idiom in many languages. I argue that you should use it when it is exactly what you mean.
 
I argue that you should use it when it is exactly what you mean.

The problem is that there's no particularly good definition for what it means to mean inheritance. The closest I've ever seen is the Liskov Substitution Principle, and that has a lot of ambiguity to it.
 
there's no particularly good definition for what it means to mean inheritance.

I beg your pardon? I said use it when it is what you mean. That means you get to decide, it is not a popularity contest.

There's no particularly good definition for a lot of things in some programming languages. What does protected inheritance mean in C++? When should you use if and when should you use unless in Ruby?

Quite frankly, consensus on these things often boil down to whether someone has written a book telling you what it ought to mean, like the GoF did with Design Patterns.

If the book is easy to read, and comes along at just the right time when a new language is taking off, and the planets are aligned just so, a great mass of people will parrot its contents without any idea of what it means.

And then we can truthfully say that there is agreement on what such-and-such means, because we have confidence that someone else will recognize it.

In the mean time, I will go along my own way just deciding what it ought to mean in a particular code base given the constraints on that particular code and the things that code needs to do.

I guess I embrace TIMTOWTDI. You may not, you may prefer that everything has exactly one meaning everyone agrees on.
 
I totally agree with what you're saying in the last post, but your earlier comment -- I argue that you should use it when it is exactly what you mean. -- implies there's some kind of exact meaning that you can exactly mean. And that's just not true. But that implication was apparently just in my head.
 
Robert:

Meaning is such a big word. There is "I meant to do that," meaning (ha) that you want a certain result, and the code means no more and no less than whatever it happened to do.

Almost all software is deterministic enough that it always has that kind of meaning with exactitude.

Another meaning of "meaning" is something along the following lines: you have in your mind a set of constraints on the design of the software, such that you can look at the software and decide whether it is consistent with your constraints or not.

So--for example--if you decide that inheritance "means" Liskoiv equivalence in a strong sense, then you might see "A is-a B" in one part of the code. But later you see a place where A is modified such that it cannot substitute for B, and you feel this violates the meaning of A is-a B.

If that is your model for "meaning," then the expression "exact meaning" describes the precision of your test for consistency.

if there are a lot of places where you say "this is mostly consistent," or "sorta wrong," then you do not have an exact meaning.

What I was trying to say earlier is that there is a third sense of "exact meaning," which is the One Way vs. Many Ways argument. This is a little orthogonal: if tehre are many programmers, each of whom have an exact meaning for "is-a" in the sense above, but their meanings are different, some people say there is no exact meaning.

That is a third sense of meaning, a sense of consensus.

Any ways, I am trying to say that I have strong opinions about IS-A in the second sense, I believe you can establish a concrete standard on a team or on a code base. I agree that there is lively debate across teams and programmers. And I agree that matters very much to the people who want there to be one way to do it.
 
Robert Fischer said:

The problem is that there's no particularly good definition for what it means to mean inheritance

In Object Oriented Software Construction, Bertrand Meyer defines twelve different categories of inheritance, conveniently grouped into three broad families:

MODEL INHERITANCE

Subtype Inheritance
View Inheritance
Restriction Inheritance
Extension Inheritance

VARIATION INHERITANCE

Functional Variation Inheritance
Type Variation Inheritance
Uneffecting Variation Inheritance

SOFTWARE INHERITANCE

Reification Inheritance
Structure Inheritance
Implementation Inheritance
Facility Inheritance
Constant Inheritance
Machine Inheritance
 
From Object Oriented Software Construction:

The extension-specialization paradox
Inheritance is sometimes viewed as extension and sometimes as specialization. Although
these two interpretations appear contradictory, there is truth in both — but not from the
same perspective.

It all depends on whether you look at a class as a type or a module. In the first
case, inheritance, or is, is clearly specialization; “dog” is a more specialized notion than
“animal”, and “rectangle” than “polygon”. This corresponds, as noted, to subset inclusion:
if B is heir to A, the set of run-time objects represented by B is a subset of the
corresponding set for A.
But from the module perspective, where a class is viewed as a provider of services, B
implements the services (features) of A plus its own. Fewer objects often allows more
features, since it implies a higher information value; going from arbitrary animals to dogs
we can add the specific property of barking, and from arbitrary polygons to rectangles we
can add the feature diagonal. So with respect to features implemented the subsetting goes
the other way: the features applicable to instances of A are a subset of those for instances of B.
Inheritance, then, is specialization from the type viewpoint and extension from the
module viewpoint. This is the extension-specialization paradox: more features to apply,
hence fewer objects to apply them to.
 
The biggest problem with favoring composition over inheritance is that, while it is a well-known and generally accepted principle, I have yet to see a mainstream programming language that makes composition as easy as inheritance, even though it really wouldn't be a difficult language feature to implement. This results in mediocre and/or deadline-constrained programmers using inheritance instead of composition just so they don't have to pound out all the manual delegation code.
 




<< Home
Reg Braithwaite


Recent Writing
Homoiconic Technical Writing / raganwald.posterous.com

Books
What I‘ve Learned From Failure / Kestrels, Quirky Birds, and Hopeless Egocentricity

Share
rewrite_rails / andand / unfold.rb / string_to_proc.rb / dsl_and_let.rb / comprehension.rb / lazy_lists.rb

Beauty
IS-STRICTLY-EQUIVALENT-TO-A / Spaghetti-Western Coding / Golf is a good program spoiled / Programming conventions as signals / Not all functions should be object methods

The Not So Big Software Design / Writing programs for people to read / Why Why Functional Programming Matters Matters / But Y would I want to do a thing like this?

Work
The single most important thing you must do to improve your programming career / The Naïve Approach to Hiring People / No Disrespect / Take control of your interview / Three tips for getting a job through a recruiter / My favourite interview question

Management
Exception Handling in Software Development / What if powerful languages and idioms only work for small teams? / Bricks / Which theory fits the evidence? / Still failing, still learning / What I’ve learned from failure

Notation
The unary ampersand in Ruby / (1..100).inject(&:+) / The challenge of teaching yourself a programming language / The significance of the meta-circular interpreter / Block-Structured Javascript / Haskell, Ruby and Infinity / Closures and Higher-Order Functions

Opinion
Why Apple is more expensive than Amazon / Why we are the biggest obstacles to our own growth / Is software the documentation of business process mistakes? / We have lost control of the apparatus / What I’ve Learned From Sales I, II, III

Whimsey
The Narcissism of Small Code Differences / Billy Martin’s Technique for Managing his Manager / Three stories about The Tao / Programming Language Stories / Why You Need a Degree to Work For BigCo

History
06/04 / 07/04 / 08/04 / 09/04 / 10/04 / 11/04 / 12/04 / 01/05 / 02/05 / 03/05 / 04/05 / 06/05 / 07/05 / 08/05 / 09/05 / 10/05 / 11/05 / 01/06 / 02/06 / 03/06 / 04/06 / 05/06 / 06/06 / 07/06 / 08/06 / 09/06 / 10/06 / 11/06 / 12/06 / 01/07 / 02/07 / 03/07 / 04/07 / 05/07 / 06/07 / 07/07 / 08/07 / 09/07 / 10/07 / 11/07 / 12/07 / 01/08 / 02/08 / 03/08 / 04/08 / 05/08 / 06/08 / 07/08 /