"I have a mind like a steel... uh... thingy." Patrick Logan's weblog.

Search This Blog

Thursday, August 12, 2004

Courtesy Implementations

Martin Fowler discusses Courtesy Implementations in Java-like languages. (Oddly he uses Ruby, which is capable of the solution presented in Smalltalk below.) These are more easily done in languages that trust you to extend the "system" classes. Moreover...

What if the Box can contain other kinds of objects that don't inherit from Node?

Object>>elephantCount
    "This is only installed in your boxed elephant application."
    ^0

Elephant>>elephantCount
    ^1

Box>>elephantCount
    ^children inject: 0 into: [ :count :child | count + child elephantCount]

8 comments:

Ian Bicking said...

This always bothered me. A more common case in Smalltalk is something like isNil; Object defines isNil as false, nil defines it as true. Seems really clever, but also really messy; all of the sudden Object has to know about every type of anything you may encounter? It breaks modularity.

I'm not sure what I'm getting to there. Oh, right, things like elephantCount. In Python you'd probably do something like (excusing the formatting):

def elephantCount(obj):
. if hasattr(obj, '__elepantCount__'): return obj.__elephantCount__()
. else: return 0

This is where magic methods come in -- they usually signal a place where in another language you might add something to Object.

I've seen some places in Twisted where they are using interfaces and adaptation because interfaces have namespaces. While you could attach any arbitrary instance variable to an object, it would have to be named, and the names might clash. Plus it's harder to backtrack given a name, and probably some other things I haven't thought of. Anyway, this offers another technique to do something akin to magic methods, only perhaps better. In that case you might create an interface IElephantCounter, which could be registered on Box and Elephant without modifying either class, and could even have a fallback adapter for all other objects.

Anyway, I always felt uncomfortable adding methods to Object in Smalltalk. For some reason magic methods seem better to me -- though I'm not entirely sure why -- but adapters are perhaps better still.

Anonymous said...

I'm no more than a newbie, but I don't get why you should use a magic method. For example, in the Box class you could write:

def numElephants(self):
..result = 0
..for child in self.children:
....if hasattr(child, 'numElephants'):
......result += child.numElephants()
..return result

Arguably, anyway, this is a kind of "instance checking", even if the focus is on an implemented method rather than an implemented interface or extended class. But the concept of some kind of "guard if-clause" still remains.

What about the Smalltalk approach leading to lots of courtesy implementations in Object? Is that a Good Thing (TM)?

Patrick Logan said...

Ian,

I think your example breaks modularity in the sense that you've dictated one way to compute the elephant count. You cannot dynamically dispatch to an object that has an alternate way, i.e. that does not refer to the elephant attribute.

The Object class does not have to know anything about other classes and elephant counts. The definition here indicates the default count is "no elephants". If some specialization of Object has elephants, the refined definition goes there.

That seems as modular and loosely coupled. All objects can count elephants. The default is zero.

Patrick Logan said...

Is attribute checking the same as instance checking?

Not quite because you can more flexibly assign attributes than classness. But I'd rather view counting as a code organization problem. (Put the default code in Object.) Rather than an attribute problem. (Put all the attributes in all the objects.)

Patrick Logan said...

Oops. Did not read Ian's code correctly... he still has a method for refinement. It's just dispatched via the procedure.

OK -- I am 50/50 on that. Python has procedures for that. Smalltalk has Object. I guess I like that I can browse Object and see all the default methods.

Anonymous said...

"Oddly he uses Ruby"

Why do you find it odd that Martin Fowler used Ruby for his example?

Anonymous said...

There's a typo in the link, should be
http://www.martinfowler.com/bliki/CourtesyImplementation.html

Isaac Gouy said...

interface ICount {}

int count(ICount a) = 0;

count(Elephant a) = 1;

count(Box a) = a.children.foldRight(
(ICount i, int count) => {return i.count + count;}, 0);

Blog Archive

About Me

Portland, Oregon, United States
I'm usually writing from my favorite location on the planet, the pacific northwest of the u.s. I write for myself only and unless otherwise specified my posts here should not be taken as representing an official position of my employer. Contact me at my gee mail account, username patrickdlogan.