Python Design Patterns - composition vs. inheritance

188 views
Skip to first unread message

snew...@gmail.com

unread,
Nov 15, 2007, 3:28:28 PM11/15/07
to
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'.

Since this is all new and I'm still learning, I was hoping someone can
give me some pointers on best practices on applying these ideas. If my
logic is incorrect on anything, please don't hesitate to tell me where
I'm wrong - I'm completely open to any help offered.

As a very simplified example, if I had two classes, Pet and Owner, it
seems that I would not have Pet inherit from Owner, since a pet 'has
an' owner, but not 'is an' owner. If this is correct, does my code
below reflect this? I passed the owner object into the pet object's
constructor - is this the right way to do it?

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()?

Thanks in advance to anyone who can share their experience with me -
it's very appreciated.

class Owner(object):
def __init__(self):
self.address = '123 Fake Street'

def get_foo(self):
return 'bar'

class Pet(object):
def __init__(self, owner):
self.owner = owner

def __getattr__(self, name):
if name == 'address':
return self.owner.address
else:
raise AttributeError, name

def get_foo(self):
return self.owner.get_foo()

owner = Owner()
pet = Pet(owner)

Diez B. Roggisch

unread,
Nov 15, 2007, 3:37:16 PM11/15/07
to

> As a very simplified example, if I had two classes, Pet and Owner, it
> seems that I would not have Pet inherit from Owner, since a pet 'has
> an' owner, but not 'is an' owner. If this is correct, does my code
> below reflect this? I passed the owner object into the pet object's
> constructor - is this the right way to do it?

Yes. Of course there are other ways, establishing the connection later,
and of course making the Owner know her pets. But your unidirectional,
ctor-passed implementation is sensible.

> 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()?

No, that's certainly not a good idea. And I'm under the impression you
misunderstood something there in the original lecture/explanation.

The reason is simple: by adding these methods, you essentially extend
Pet's knowledge as a class to what only its owner has to know. Which
makes no sense. Why should pets know about addresses? Or cars? Or
anything else that belongs to the owner only.

Of course there are just reasons to create such delegation methods - if
for example the property/method is of general interest to the Pet, but
implemented by means of the owner. But I've got difficulties even to
imagine such thing, at least in your actual example.

Diez

Carl Banks

unread,
Nov 15, 2007, 4:49:10 PM11/15/07
to
On Nov 15, 3:37 pm, "Diez B. Roggisch" <de...@nospam.web.de> wrote:

> > 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()?
>
> No, that's certainly not a good idea. And I'm under the impression you
> misunderstood something there in the original lecture/explanation.

Having read lots of "perspectives" on OO style, it wouldn't surprise
me if the OP understood the point perfectly.

I.e., there probably are people out there who suggest that when you
have something like this:

mypet.owner.get_attributes()

you should reverse-refactor it into something like this:

mypet.get_owner_attributes()


I would agree that the former is preferable, especially in cases like
this one where the classes aren't really closely related.


Carl Banks

snew...@gmail.com

unread,
Nov 15, 2007, 5:10:26 PM11/15/07
to
> Yes. Of course there are other ways, establishing the connection later,
> and of course making the Owner know her pets. But your unidirectional,
> ctor-passed implementation is sensible.

I think my main concern while getting my toes wet on this was to not
reference the owner object out of "thin air" but to pass it in when
pet is instantiated. I'm not sure what 'actor-passed' is yet, but it
gives me something to search for and learn about.

I'd love to see other/better/different implementations if anyone wants
to enlighten me. What would a non-unidirectional (bidirectional?) look
like or accomplish? Does that mean that in the example I provided, you
could make the owner aware of their pets? That's something that is not
available using inheritance, if I understand correctly.

> No, that's certainly not a good idea. And I'm under the impression you
> misunderstood something there in the original lecture/explanation.

That wouldn't surprise me if I misunderstood it :) I've watched Alex
Martelli's Google Tech talk a half-dozen times and it's only now
starting to make sense. It's hard to apply some of the available
material's examples to Python since a lot of the documentation I find
is specific to implementations in lower-level languages and don't
apply to Python. (such as the Strategy pattern?)

My understanding was that using __getattr__ was either called
delegation or a Command pattern, and this was hiding/encapsulating the
specifics of the implementation. I'd like to be corrected if I'm
wrong, or if I'm two blocks off Main Street with this.

> The reason is simple: by adding these methods, you essentially extend
> Pet's knowledge as a class to what only its owner has to know. Which
> makes no sense. Why should pets know about addresses? Or cars? Or
> anything else that belongs to the owner only.

> Of course there are just reasons to create such delegation methods - if
> for example the property/method is of general interest to the Pet, but
> implemented by means of the owner. But I've got difficulties even to
> imagine such thing, at least in your actual example.

Yeah, I was struggling to come up with a decent example - a pet's
address was about the best example of a delegated property I could
think of. If someone has a better set of objects that make sense, let
me know and I'll probably feel less foolish asking.

Thanks for your help! It's truly appreciated.

Scott

Diez B. Roggisch

unread,
Nov 15, 2007, 5:25:07 PM11/15/07
to
> I think my main concern while getting my toes wet on this was to not
> reference the owner object out of "thin air" but to pass it in when
> pet is instantiated. I'm not sure what 'actor-passed' is yet, but it
> gives me something to search for and learn about.

I meant ctor, short-hand for constructor.

> I'd love to see other/better/different implementations if anyone wants
> to enlighten me. What would a non-unidirectional (bidirectional?) look
> like or accomplish? Does that mean that in the example I provided, you
> could make the owner aware of their pets? That's something that is not
> available using inheritance, if I understand correctly.


Its simple.

class Owner(object):
def __init__(self):
self.pets = []

class Pet(object):
def __init__(self, owner):
self.owner = owner

owner.pets.append(self)

>> No, that's certainly not a good idea. And I'm under the impression you
>> misunderstood something there in the original lecture/explanation.
>
> That wouldn't surprise me if I misunderstood it :) I've watched Alex
> Martelli's Google Tech talk a half-dozen times and it's only now
> starting to make sense. It's hard to apply some of the available
> material's examples to Python since a lot of the documentation I find
> is specific to implementations in lower-level languages and don't
> apply to Python. (such as the Strategy pattern?)
>
> My understanding was that using __getattr__ was either called
> delegation or a Command pattern, and this was hiding/encapsulating the
> specifics of the implementation. I'd like to be corrected if I'm
> wrong, or if I'm two blocks off Main Street with this.

I don't know that talk. Maybe you can give us the link, so we can see
for ourselves?

There is no doubt about Alex' being a profound teacher of software
design. But yet I think your example doesn't capture what you think he
wanted to present.

Delegation of course is a well-known pattern. It applies at
circumstances that are manyfold, e.g. wehn you try create a proxy for
purposes of tracking or filtering calls.


Diez

Chris Mellon

unread,
Nov 15, 2007, 5:45:23 PM11/15/07
to pytho...@python.org

An example might be something like pet.has_foodbowl, which is
implemented as self.owner.has_foodbowl_for(self). If you've got pets
with more than one owner, it might be any(owner.has_foodbowl_for(self)
for owner in self.owners) - certainly a candidate for an easy accessor
in the Pet class.

This is more an example of API convenience than it is any sort of
general OO or type theory concept. Note that any time you do this sort
of reverse binding you're codifying assumptions about your object
model (by which I mean domain objects, rather than your inheritance
hierarchy) into your code.

I don't worry to much about is-a or has-a or any sort of strict class
hierarchy or type system. I view inheritance, in Python, as a way of
inheriting implementation and I do it when I want some other classes
implementation and not otherwise. In other languages (not C++ family,
but ML family) I think much more in terms of types and type systems,
but in Python I stay pragmatic and flexible.

Carl Banks

unread,
Nov 15, 2007, 7:57:57 PM11/15/07
to
On Nov 15, 3:28 pm, "snewma...@gmail.com" <snewma...@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

Message has been deleted
Message has been deleted

A.T.Hofkamp

unread,
Nov 16, 2007, 7:49:37 AM11/16/07
to
On 2007-11-15, snew...@gmail.com <snew...@gmail.com> wrote:
> inheritance when an object's relationship to another object is 'is-a'
> and composition when the relationship is 'has-a'.
>
> Since this is all new and I'm still learning, I was hoping someone can
> give me some pointers on best practices on applying these ideas. If my
> logic is incorrect on anything, please don't hesitate to tell me where
> I'm wrong - I'm completely open to any help offered.

>
> As a very simplified example, if I had two classes, Pet and Owner, it
> seems that I would not have Pet inherit from Owner, since a pet 'has
> an' owner, but not 'is an' owner. If this is correct, does my code
> below reflect this? I passed the owner object into the pet object's
> constructor - is this the right way to do it?

This is indeed one way. One of the things you enforce in this way that there is
no Pet object without an owner.

> 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

As with most design stuff, they are right and they are also wrong. It's a
trade-off.

They are right in the sense that if you change the structure of your links, you
will break potentionally a lot of code. Imagine you have to ask a data base for
the owner rather than having a direct link. All "pet.owner" references will
need to be changed then. If you hide the connection inside your Pet, there is
only one place that needs to be changed.

They are wrong in the sense that it is not always appropiate for a Pet object
to perform such a function. By hiding "self.owner.address" inside Pet, you are
designing a smart pet that knows how to find the address of its owner.
Unfortunately, smarter also means more complex, and at some point you will
defeat the advantage of OO, namely spreading responsibilities, and having
several simple objects that work together to realize your application.

So there is a trade-off. There is no universal rule, it highly depends on your
application, your future plans, and the lifetime of the app (to name a few).

> 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'd say no.
One of the 'rules' (guide lines) of Python is "Be explicit rather than
implicit" [1]. You may save a few dots, on the other hand, you obfuscate how
the link is realized (that is "pet.address" doesn't say it uses owner to make
the connection, unlike "pet.owner.address").
In the long run, the latter may be more important.


In general, I think you shouldn't need advanced trickery like __getattr__() for
your design. If you do, your design is most likely wrong.

[1]: The Zen of Python: http://www.python.org/dev/peps/pep-0020/


Sincerely,
Albert

Carl Banks

unread,
Nov 16, 2007, 10:35:27 AM11/16/07
to
On Thu, 15 Nov 2007 21:25:16 -0800, Dennis Lee Bieber wrote:

> On Thu, 15 Nov 2007 16:57:57 -0800 (PST), Carl Banks
> <pavlove...@gmail.com> declaimed the following in comp.lang.python:


>
>> 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.
>>

> Well, you would if you override the two set_* methods to set both
> height and width to the same value <G>

But that breaks expectations: a user doesn't expect set_width() to affect
the height.

In some cases it might be ok to derive Square from Rectangle. For
instance, if you're selecting an area of the screen with a mouse, you
could define a Square class to select square regions: it would have
independent height and width attributes but the actual selected area
would be the largest square-shape that fits inside.

Carl Banks

Neil Cerutti

unread,
Nov 16, 2007, 12:02:22 PM11/16/07
to
On 2007-11-16, Dennis Lee Bieber <wlf...@ix.netcom.com> wrote:
> On Thu, 15 Nov 2007 12:28:28 -0800 (PST), "snew...@gmail.com"
><snew...@gmail.com> declaimed the following in comp.lang.python:

>
>>
>> As a very simplified example, if I had two classes, Pet and Owner, it
>> seems that I would not have Pet inherit from Owner, since a pet 'has
>> an' owner, but not 'is an' owner. If this is correct, does my code
>> below reflect this? I passed the owner object into the pet object's
>> constructor - is this the right way to do it?
>>
> Well, other than the facet that I'd say "Owner" has-a "Pet", but
> this example is a bit too disjoint for the association (IE, it goes both
> ways)
>
> A common example is something like:
>
> class Vehicle(object):
> #which has attributes for speed, direction, cargo capacity,
> passenger capacity
>
> class RoadVehicle(Vehicle):
> #inherits (RoadVehicle IS-A Vehicle with Axles)
>
> class Auto(RoadVehicle)
> # HAS-A Front-Axle, Rear-Axle
> # and Axle HAS-A Left-Tire, Right-Tire (unless a Dual -- with
> inner left, outer left, inner right, outer right, those those would
> appear on Bus(RoadVehicle) and Truck(RoadVehicle) classes)
>
> Look up "Law of Demeter" (I believe that is the name). The idea
> is that a "user" of, say, Auto, should NOT need to know that it
> has tires (may know it has axles, so it may have a
> "listAxles()" method, which returns a list/tuple of the axles
> that the Auto has. You could then use that list to access the
> tires on each axle { for tire in aRoadVehicle.listAxles():
> print tire.specification() }

The Law of Demeter constrains the attributes that the methods of
Auto may invoke. Given the above containment hierarchy, the Law
says that no method of Auto may depend on any attribute or method
of Tire. So if you needed to compute, in an Auto method,
something that depends on an attribute of Tire, you must make
that information available directly through the Axle interface.

The benefit is that this lowers the coupling between classes. The
Tire interface can change completely without affecting the Auto
class.

> BUT... Just look at the Python library... sys.stdout.write(),
> if following Law of Demeter would turn into:
>
> myStdout = sys.getStdout()
> myStdout.write()

The Law of Demeter doesn't apply to that example.

--
Neil Cerutti

Bruno Desthuilliers

unread,
Nov 16, 2007, 12:52:14 PM11/16/07
to
snew...@gmail.com a écrit :

> 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'.

wrt/ inheritance, it only makes sens with declarative static type
systems where polymorphic dispatch depends on subclassing (so
inheritance is used for both implementation and subtyping). In the
context of a dynamic type system, inheritance is only for implementation
reuse, ie given the following class definitions :

class Foo(object):
def bar(self):
return 42

class Baaz(object):
def bar(self):
return 84


Baaz actually 'is a' (proper subtype of) Foo (and vice-versa).

>
> Since this is all new and I'm still learning, I was hoping someone can
> give me some pointers on best practices on applying these ideas. If my
> logic is incorrect on anything, please don't hesitate to tell me where
> I'm wrong - I'm completely open to any help offered.
>
> As a very simplified example, if I had two classes, Pet and Owner, it
> seems that I would not have Pet inherit from Owner, since a pet 'has
> an' owner, but not 'is an' owner.

Both (I mean 'has a' and 'is a') are not necessary exclusive.

> If this is correct, does my code
> below reflect this? I passed the owner object into the pet object's
> constructor - is this the right way to do it?

Here again, it depends on the lifetime cycles of both objects. Look for
the difference between composition and aggregation. And it of course
depends on the problem you're trying to solve - there's no such thing as
an 'absolute' model.

> 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?

"should", "should not", laws and golden rules... Indeed, it's usually
better to respect encapsulation.

> Ideally, should I be writing
> getattr() methods so I can do pet.address instead of
> pet.owner.address?

What's the use of pet.address ? Is really 'adress' a responsability of
'Pet' ?

> Should I be doing the same with owner's methods
> like I did with get_foo()?

Python's methods actually are attributes, so you don't (technically)
need a distinct scheme for methods - __getattr__ will work as well.

__getattr__ is fine when you really have a generic delegation (look for
the Proxy pattern for an example). Else (ie, you decide that, for this
given problem, 'address' is really a responsability of Pet), you might
be better defining an explicit 'address' attribute in Pet - preferably
using a computed attribute (either a property object or a custom
descriptor).

Anyway, the simplest your design and implementation, the better.


My 2 cents...

Bruno Desthuilliers

unread,
Nov 16, 2007, 1:14:53 PM11/16/07
to
snew...@gmail.com a écrit :
(snip)

> It's hard to apply some of the available
> material's examples to Python since a lot of the documentation I find
> is specific to implementations in lower-level languages and don't
> apply to Python.

Fact is that quite a few design patterns are mostly workaround the
low-level and lack of dynamism of the C++/Java family. In higher-level
dynamic languages like Python, Ruby etc, they are not "patterns", they
are idioms with a good builtin support !-)

> (such as the Strategy pattern?)

The "Strategy" pattern is mostly about delegating (part of) a behaviour
to someone else. As such, it can be found (one way or another) in almost
any language - even in C with function pointers (look at how the C
stdlib sort() function works...).

> My understanding was that using __getattr__ was either called
> delegation or a Command pattern,

Now we have proper computed attributes, __getattr__ is mainly used for
delegation, yes. But some years ago, it was also the way to implement
computed attributes.

(snip)

>> Of course there are just reasons to create such delegation methods - if
>> for example the property/method is of general interest to the Pet, but
>> implemented by means of the owner. But I've got difficulties even to
>> imagine such thing, at least in your actual example.
>
> Yeah, I was struggling to come up with a decent example - a pet's
> address was about the best example of a delegated property I could
> think of. If someone has a better set of objects that make sense, let
> me know and I'll probably feel less foolish asking.


class Forbidden(Exception): pass

class Interceptor(object):
def __init__(self, obj, allow_access):
self.__obj = obj
self.__allow_access = allow_access

def __getattr__(self, name):
if self.__allow_access(self.__obj, name):
return getattr(self.__obj, name)
else:
raise Forbidden

As Carl mentioned, real-life code usually uses more technical objects
than domain objects.


Odalrick

unread,
Nov 17, 2007, 4:10:27 AM11/17/07
to
On 16 Nov, 16:35, Carl Banks <pavlovevide...@gmail.com> wrote:
> On Thu, 15 Nov 2007 21:25:16 -0800, Dennis Lee Bieber wrote:
> > On Thu, 15 Nov 2007 16:57:57 -0800 (PST), Carl Banks
> > <pavlovevide...@gmail.com> declaimed the following in comp.lang.python:

>
> >> 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.
>
> > Well, you would if you override the two set_* methods to set both
> > height and width to the same value <G>
>
> But that breaks expectations: a user doesn't expect set_width() to affect
> the height.

I can't speak for everyone but I certainly expect setting the width of
a Square to change it's height. In fact, that would probably be the
reason I used a Square rather than a Rectangle in the first place.

Neil Cerutti

unread,
Nov 17, 2007, 10:57:14 AM11/17/07
to
On 2007-11-17, Odalrick <odal...@hotmail.com> wrote:
>> But that breaks expectations: a user doesn't expect
>> set_width() to affect the height.
>
> I can't speak for everyone but I certainly expect setting the
> width of a Square to change it's height. In fact, that would
> probably be the reason I used a Square rather than a Rectangle
> in the first place.

Some of the dissonance is from assuming that rectangles can
mutate themselves. I've never seen one do that! ;) The rest of
the dissonance is caused by strong typing. A rectangle with equal
width and height is still a rectangle, in a strongly typed
language.

--
Neil cerutti

Carl Banks

unread,
Nov 17, 2007, 1:58:06 PM11/17/07
to

You're missing the point. It's not whether a Square behaves like a
Square. Of course it does. It's whether a Square behaves like a
Rectangle. It doesn't.

A Rectangle you would expect to be able to set the width and height of
independently. A Square you cannot set the width and height of
independently. A Square meets the expectations of a Square, but it does
not meet the expectations of a Rectangle. Therefore a Square is not a
Rectangle.

The whole point of subtyping is the ability to use objects of the subtype
in place of objects of the base type. In other words, you should be able
to use a Square anywhere you use a Rectangle. But you can't, because
Squares don't act like Rectangles, and that defeats the whole benefit of
polymorphism.


Here's an example of what I mean. Consider this function, designed for a
Rectangle:

def enclose(rect,points):
"Set rect to be large enough to enclose all the given points."
x,y = points[0]
xmin = xmax = x
ymin = ymax = y
for x,y in points:
if x > xmax: xmax = x
elif x < xmin: xmin = x
if y > ymax: ymax = y
elif y < ymin: ymin = y
rect.set_width(max(xmax-xmin,1))
rect.set_height(max(ymax-ymin,1))


Now, if Square is a subtype of Rectangle, one would expect a Square to be
usable wherever a Rectangle is. But for this function it's not: passing
in a Square will break this function.

Bringing this back to my original point: the "is a" test does not account
for good substitutability behavior in computer models of objects. The
geometric shape square is a geometric shape rectangle, but that does not
mean that the computer representation of a Square is a computer
reperesentation of a Rectangle. The "is a" test fails us here. Which is
why I don't like it.

Google for Liskov Substitutability if you are interested. I didn't pull
this idea out of my hat. In fact I learned the term from reading a post
by GvR himself, though the idea was intuitive to me long before that.

Carl Banks

Carl Banks

unread,
Nov 17, 2007, 2:16:39 PM11/17/07
to
On Sat, 17 Nov 2007 15:57:14 +0000, Neil Cerutti wrote:
> On 2007-11-17, Odalrick <odal...@hotmail.com> wrote:
>>> But that breaks expectations: a user doesn't expect set_width() to
>>> affect the height.
>>
>> I can't speak for everyone but I certainly expect setting the width of
>> a Square to change it's height. In fact, that would probably be the
>> reason I used a Square rather than a Rectangle in the first place.
>
> Some of the dissonance is from assuming that rectangles can mutate
> themselves. I've never seen one do that! ;)


A rectangle can't do that, but a Rectangle very often can.


> The rest of the dissonance
> is caused by strong typing. A rectangle with equal width and height is
> still a rectangle, in a strongly typed language.


I don't think strong typing has much to do with it. In a duck-typed
language like Python we're not limited to subtypes for polymorphism--we
can replace any type with any other as long as it has the right methods
and attributes. This loosens things up a bit, and it's not so important
that objects are 100% substitutable any more.

But even with Python's duck typing, subtypes should still try to be fully
substitutable for the base types, because of the blessed nature of the
relationship. Subtypes inherit methods from the base type, and those
methods should continue to behave appropriately.

(This is different from duck-typing of unrelated types, because unrelated
types would simply not implement the methods that don't make sense. So
if you have independent Square and Rectatngle classes, Square would
implement just set_side_length(), and Rectangle would implement just
set_width() and set_height().)

Carl Banks

Odalrick

unread,
Nov 18, 2007, 12:43:35 PM11/18/07
to
On 17 Nov, 19:58, Carl Banks <pavlovevide...@gmail.com> wrote:

> Google for Liskov Substitutability if you are interested. I didn't pull
> this idea out of my hat. In fact I learned the term from reading a post
> by GvR himself, though the idea was intuitive to me long before that.
>
> Carl Banks

Interesting... I think I've actually had problems in the past beacause
I did not understand this.

Reply all
Reply to author
Forward
0 new messages