Re: Composition vs. Inheritance (Was: colored point redux)
Daniel Yoo <[email protected]> Mon, 26 Feb 2007 13:35:40 -0500 (EST)
| Newsgroups | gmane.comp.lang.lightweight |
|---|---|
| Message-ID | <[email protected]> |
> Perhaps the strength of extension is really in the other scenario I
> mentioned: the case where, for example, an Employee is also a Person,
> and thus inherits all fields and methods from Person. How would you
> implement that with composition? I've seen people recommend something
> like
>
> class Employee {
> Person person;
>
> ...
> }
>
> which would then be used like
>
> anEmployee.person.someMethod(...)
>
> but that feels to me like taking a step back from object-oriented
> programming, throwing away its advantages.
Hi Bob,
Ok, so we'd like an Employee to look like a Person then? Employee can
dispatch methods off to the Person object.
/******************************************/
class Employee {
Person person;
Object someMethod(...) {
return person.someMethod(...);
}
...
}
/******************************************/
I don't think it's something that conflicts with OOP programming: it's
just stateful agents talking to each other across interfaces. This does
bring up the question of multiple composition again, though. What do we
do with:
/******************************/
class Employee {
Person person1, person2;
...
}
/******************************/
where it's no longer completely obvious what we mean by
Employee.someMethod()? Abstractly, this feels like the whole problem
behind multiple inheritance, and I'm not sure what the best solution to
this is besides explicitely writing out what Employee.someMethod() means.
Writing out the dispatch functions isn't technically a big problem,
although it is extra typing. It's a one-time cost, and pretty much
no-brain typing that could be automated through something like macros.
Again, to make this very concrete, here's an example of this in mzscheme,
to handle most of the ugly typing for this proxying thing.
(The following example is probably similar to mzlib's surrogate.ss
described in:
http://download.plt-scheme.org/doc/360/html/mzlib/mzlib-Z-H-44.html#node_chap_44)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(module proxying-example mzscheme
(require (lib "class.ss"))
(define-syntax (init-field&dispatch stx)
(syntax-case stx ()
[else (raise-syntax-error #f "don't use me outside of proxy" stx)]))
(define-syntax (proxy stx)
(define (do-expansion sub-stx)
(syntax-case sub-stx ()
[(_ (f val) names ...)
(let ([accessors
(map (lambda (acc)
`(define/public (,acc . args)
(send/apply ,(syntax-e (syntax f)) ,acc args)))
(syntax-e (syntax (names ...))))])
(datum->syntax-object
stx
`((init-field ,(syntax (f val)))
,@accessors)))]
[else (raise-syntax-error #f "do-expansion" stx sub-stx)]))
(syntax-case stx (init-field&dispatch)
[(_ cl% parent%
(field&dispatch f names ...)
body ...)
(with-syntax
([(expanded-field&accessors ...)
(do-expansion (syntax-e (syntax (field&dispatch f names
...))))])
(syntax/loc stx
(class parent%
expanded-field&accessors ...
body ...)))]))
;; Example below:
(define person%
(class object%
(init name)
(define -name name)
(define/public (say-hello)
(printf "hello world, my name is ~a~n" -name))
(define/public (get-name)
-name)
(super-new)))
(define employee%
(proxy person% object%
(init-field&dispatch (person #f) say-hello get-name)
(define/public (say-goodbye)
(printf "goodbye from ~a~n" (get-name)))
(super-new))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Basically, the top of this example defines an extremely messy macro that
does the hard work of saying that an employee% takes in a person, and
whatever we want to dispatch to an employee gets passed to its inner
person.
Unfortunately, just as the macro is doing no-brain work, I'm also a bit of
a no-brain myself, so the macro below I just kludged together is ugly and
not quite right (and I think surrogate.ss does pretty much what this thing
does).
Still, it does sorta work:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
> (define e (new employee% [person
(new person% [name "Robbert"])]))
> (send e say-hello)
hello world, my name is Robbert
> (send e say-goodbye)
goodbye from Robbert
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
The idea is that employee% proxies a person, and I'm telling the class
system that I'm willing to have say-hello and get-name delegate off to the
person's methods. The macro does the hard work of expanding out the
one-liner:
(init-field&dispatch (person #f) say-hello get-name)
into extremely repetitive code:
(init/field [person f])
(define/public (say-hello . args)
(send/apply person say-hello args))
(define/public (get-name . args)
(send/apply person get-name args))
The point I'm trying to make, though, is that the and the silly amount of
typing we have to do delegation is something that can be mostly
eliminated, since it's just textual gruntwork.
Another tangent: perhaps one could do something like "environmental
acquisition" to get much of the advantages of inheritance? It feels like
there's something in
http://www.ccs.neu.edu/home/lorenz/research/acquisition/ that relates to
what you're bringing up; I haven't thought about it deeply yet.
Best of wishes!