Python Design Patterns - composition vs. inheritance

Carl Banks pavlovevidence at gmail.com
Fri Nov 16 01:57:57 CET 2007


On Nov 15, 3:28 pm, "snewma... at gmail.com" <snewma... at gmail.com> wrote:
>

My response ended up being pretty long and heavy for a beginner, but
you sound pretty smart.


> In learning about design patterns, I've seen discussion about using
> inheritance when an object's relationship to another object is 'is-a'
> and composition when the relationship is 'has-a'.

I've never been a big fan of this rule, for two reasons.  First of
all, "is a" and "has a" aren't always used in the sense intended by
this rule's inventor.

To illustrate the confusion, take your Pet-Owner example.  When you
say "A Pet has an Owner", the verb "to have" has the sense of "to
possess".  But possession alone is *not* the right sense of "to have"
to suggest using containment.

(Note: I'm using containment to mean "object X owns object Y".
Referencing the object without owning it is not containment; it's just
referencing.)

The reciprocal relationship to "has a" is "is a part of", and I rather
prefer that way of putting it.  The Pet has an Owner, but the Owner is
not part of the Pet, so "has a" is being used in the wrong sense.
OTOH, a Car has an Engine, and an Engine is part of a Car, so "has a"
is being used in the right sense there.

This is not to say containment is not the appropriate way to implement
Pet-Owner in any case.  If this program is Pet-centric, and the Owner
exists only insofar as it is perceived by the Pet, containment might
be right way.  That's another point: sometimes external circumstances
play a part in whether inheritance or containment or something else
should be used.

FWIW, my first stab at a Pet-Owner relationship would probably look
something like this:

class Family:
    def __init__(self,humans,pets):
        self.humans = humans
        self.pets = pets

Note that a Family has Pets, and Pets are part of the Family, so
containment would be a good idea for Family-Pet relationship.  (The
Pets and Humans would have a field referring back to the Family, and
the pet could determine its owners that way.)

A source of confusion with "is a" is that it doesn't necessarily imply
a good inheritance relationship (aka Liskov substitutability).
Consider a Rectangle class that has methods set_width() and
set_height().  Should Square class inherit from Rectangle?  A Square
is a Rectangle, but it's not suitable as a subclass of Rectangle,
because the width and height can't be set independently.  You can't
substitute a Square for a Rectangle and get reasonable behavior.

Second reason I don't like "is a" and "has a": They are misleading
enough for concrete objects like Pets, Owners, Rectangles, and so on.
But real programming is often done with abstract objects like
SimulationContexts, EncryptedHTTPConnections, ItemSequencers, and
FingerProtocols.  "Is-a" and "has-a" relationships are not always
clear for classes such as these.


> Also, I've seen talk that ideally you shouldn't have too many "dots"
> in your method calls, instead using delegates to the methods and
> attributes. Can anyone elaborate on this? Ideally, should I be writing
> getattr() methods so I can do pet.address instead of
> pet.owner.address? Should I be doing the same with owner's methods
> like I did with get_foo()?

I wouldn't worry about minimizing dots.  If you find yourself often
using a certain long expressions like

self.owner.house.yard.grass_length

you might want to delegate them on a case-by-case basis by writing
methods like this:

def get_home_grass_length(self):
    return self.owner.house.yard.grass_length

But using getattr to do it automatically is tricky, error prone, and
defeats the purpose of keeping seperate namespaces.

OTOH, if you find yourself delving several dots deep a lot, it
suggests that you need to refactor you code.  Move some of the code
from the shallower classes into the deeper classes, closer to the data
it needs.


Hope this helped and didn't confuse you even more. :)  Good luck
learning.


Carl Banks



More information about the Python-list mailing list