Inheritance or composition: Rely on "is-a" and "has-a"?

19,486

Solution 1

No - "is a" does not always lead to inheritence. A well cited example is the relationship between a square and a rectangle. A square is a rectangle, but it will be bad to design code that inherits a Square class off a Rectangle class.

My suggestion is to enhance your "is a / has a" heuristic with the Liskov Substitution Principle. To check whether an inheritence relationship complies with the Liskov Substitution Principle, ask whether clients of a base class can operate on the sub class without knowing that it is operating on a sub class. Of course, all the properties of the sub class must be preserved.

In the square / rectangle example, we must ask whether a client of rectangle can operate on a square without knowing that it is a square. All that the client must know is that it is operating on a rectangle. The following function demonstrates a client that assumes that setting the width of a rectangle leaves the height unchanged.

void g(Rectangle& r)
{
    r.SetWidth(5);
    r.SetHeight(4);
    assert(r.GetWidth() * r.GetHeight()) == 20);
}

This assumption is true for a rectangle, but not for a square. So the function cannot operate on a square and therefore the inheritence relationship violates the Liskov Substitution principle.

Other examples

Solution 2

Yes and no.

The line can be blurred. This hasn't been helped by some pretty terrible examples of OO programming from the early days of OO like: Manager is an Employee is a Person.

The thing you have to remember about inheritance is: inheritance breaks encapsulation. Inheritance is an implementation detail. There's all sorts written on this subject.

The pithiest way to sum it up is:

Prefer composition.

That doesn't mean use it to the complete exclusion of inheritance. It just means inheritance is a fallback position.

Solution 3

If the relationship is "is-a" - use inheritance, and if the relationship is "has-a" - use composition. Is it always right?

In a sense, yes. But you must be careful not to introduce unnecessary, artificial "is-a" relationships.

For example, one may think that a ThickBorderedRectangle is-a Rectangle, which seems reasonable at first sight, and decide to use inheritance. But this situation is better described saying that a Rectangle has-a Border, which may or may not be a ThickBorder. In this case he would better prefer composition.

In the same fashion, one may think that a ThickBorder is-a special Border and use inheritance; but it's better to say that a Border has-a width, hence preferring composition.

In all these ambiguous cases, my rules of thumb are think twice and, as others advised, prefer composition over inheritance.

Solution 4

I don't think it's as simple as "is-a" vs "has-a" - as cletus said, the line can get very blurry. It's not like two people would always come to the same conclusion given just that as a guideline.

Also as cletus said, prefer composition over inheritance. To my mind, there have to be really good reasons to derive from a base class - and in particular, the base class really needs to have been designed for inheritance, and indeed for the kind of specialization you want to apply.

Beyond that - and this will probably be an unpopular idea - it comes down to taste and gut feeling. Over time I think good developers get a better sense of when inheritance will work and when it won't, but they may well find it hard to express clearly. I know I find it hard, as this answer demonstrates. Maybe I'm just projecting the difficulty onto other people though :)

Solution 5

Inheritance doesn't always mean there is an "is-a" relationship and the lack of one doesn't always mean there isn't.

Examples:

  • If you use protected or private inheritance then you lose external substitutability, that is, as far as anyone outside of your hierarchy is concerned a Derived is not a type of Base.
  • If you override a base virtual member and change behaviour observable from someone referencing the Base, from the original Base implementation, then you have effectively broken substitutability. Even though you have a public inheritance heirarchy Derived is-not-a Base.
  • Your class can sometimes be substituitable for another without any inheritance relationship at work. For example, if you were to use templates and identical interfaces for the appropriate const member functions then an "Ellipse const&" where the Ellipse happens to be perfectly circular could be substituitable for a "Circle const&".

What i'm trying to say here is it takes a lot of thought and work to maintain an "is-a" substitutable relationship between two types whether you use inheritance or not.

Share:
19,486
Tachikoma
Author by

Tachikoma

Software Engineer

Updated on June 29, 2022

Comments

  • Tachikoma
    Tachikoma almost 2 years

    When I design classes and have to choose between inheritance and composition, I usually use the rule of thumb: if the relationship is "is-a" - use inheritance, and if the relationship is "has-a" - use composition.

    Is it always right?

    Thank you.

  • Admin
    Admin over 15 years
    Well a Manager is an Employee and an Employee is a Person. What's your point ?
  • Steven A. Lowe
    Steven A. Lowe over 15 years
    Jon, where do you get this "designed for inheritance" mantra? This is the 3rd or 4th time I've heard you say this, and I just don't understand it. Do I have to buy and read your book to get this explained? Or should I just ask a question for you to answer so you get more rep for it? ;-) I'll do that
  • Steven A. Lowe
    Steven A. Lowe over 15 years
  • Piskvor left the building
    Piskvor left the building over 15 years
    @Iraimbilamja: Yes, a people Manager usually is-a Employee, but this is something completely different from the Manager pattern. Unfortunately, I've seen some explanations which conflated these two meanings.
  • Epaga
    Epaga over 15 years
    That is a perfect example for an explanation on WHY you should prefer composition.
  • John Nilsson
    John Nilsson over 15 years
    I fail to see why inheritance is better at code reuse than composition is?
  • John Nilsson
    John Nilsson over 15 years
    One might also think that a Rectangle doesn't have anything, but rather that some RenderableThing has a Shape or something along those lines
  • Lordn__n
    Lordn__n over 15 years
    A Manager is an Employee but a Person has (zero or more) Employee roles is a MUCH better abstraction. If your objects change type (class) that's a warning sign you're doing something wrong and that's what happens if an Employee becomes a Manager. You can argue Manager is a property of Employee too
  • Jonathan Leffler
    Jonathan Leffler over 15 years
    Note that the question originally said both "is a" and "has a" were inheritance - see comments to my answer. It is now corrected to match your assumed version, but 'other way round' was officially inaccurate.
  • strager
    strager over 15 years
    Nice examples! I wouldn't have thought of them myself, shamefully (unless I thought hard about it).
  • alterfox
    alterfox over 15 years
    I have done this before but now I know it's not a good idea, especially in languages that don't support multiple inheritance. What if later you need to reuse that code in a class that needs to belong in another hierarchy?
  • Tom
    Tom over 15 years
    @Ed - Example: TCP socket uses a file descriptor, so you might say it is-a file. However, most file APIs assume random access is possible (e.g. fseek). A TCP socket is also an IP socket, but no clients should be accessing the IP layer directly (let the TCP stack handle it).
  • Tom
    Tom over 15 years
    That also causes you to have more interdependencies within your codebase. While it's less code, it will end up being more complex due to the inability to analyze anything independently.
  • Sir Rippov the Maple
    Sir Rippov the Maple over 15 years
    I've seen a real word example where we have a Transaction class with a collection of Sources and Destinations. We subclass Withdrawal, DebitOrder, ScheduledWithdrawal, etc. but those classes constrain the Sources and Destinations to be a certain type, hence LSP is violated.
  • Sir Rippov the Maple
    Sir Rippov the Maple over 15 years
    @John - your question highlights the fact that inheritence (as it relates to the Liskov Substitution Principle) is all about behaviour. If the square was not mutable, then there would be no problem because no client can change the width or the height.
  • Lawrence Dol
    Lawrence Dol over 15 years
    I agree with Tumpi; the square/rectangle example cited is implying a problem with mutability more so than with inheritance. The is-a relationship would be well expressed by inheritance if the shape was not mutable.
  • Kirk Broadhurst
    Kirk Broadhurst over 13 years
    This is a furphy. A square might be a rectangle in mathematics, but if you define rectangle to be a quad that can height and width set independently, then square doesn't fit. In this case, rectangle actually extends square by having the additional property of two different side lengths, instead of just one.
  • Luk Aron
    Luk Aron over 3 years
    I think this is not a good example!!!!! since a rectangle may be a square in some cases, the assumption of setting the width of a rectangle leaves the height unchanged is fundamentally wrong. In my opinion, is a relationship that could be very well done in inheritance.
  • Scotty Jamison
    Scotty Jamison almost 2 years
    For those who don't like square/rectangle, how about this one instead: An ElectricCar is-a Car, but what if your Car class has a getGasMillageReport() method? No mutability here. The is-a rule fails to ask us to inspect the behaviors of the classes, while LSP does. LSP > is-a. Sure you could say this is silly, because the Car class is obviously not general enough to include the concept of an electric car, but that's the whole point of LSP, we can follow LSP to see this issue and know that either something needs to change in Car, or we can't use inheritance.