It would be useful (or at least an interesting experiment) to add to a framework I help maintain the ability to compose objects from multiple classes at runtime.
Having done the requisite digging through CPAN and thread-hunting here, I'm not sure that what I have in mind exactly has been Modularized or Discussed (which should simply be read as my having succumbed to the programmer's siren song: roll your own, roll your own).
A 10-line function that implements per-object mixins follows, with some thoughts, questions, and example usage.
Several threads here discuss multiple inheritance, decorators, mixins, and prototype-style OO programming. For example:
And several nifty CPAN modules are relevant, for example:
I dig the Self approach, and reflective programming, but I really don't want a full-blown system, here. I just want, at run time, to wham together the methods defined in several different classes. Pretty much what Class::Object does, but I'm not even willing to use a new syntax to declare methods. Pretty much what mixin does, but at run time, and maybe even a bit simpler.
Dave Rolsky's Class::ClassDecorator is probably the closest thing to what I have in mind. And I may end up using it. But conceptually, decorators aren't what I want (more about which below). And, because of what Dave is trying to do, he has to run every method call through his AUTOLOADer. It's silly to worry about that (premature optimization of the worse kind), but I mention it because, well, that's how I am.
Mostly -- and perhaps this is a bit of mis-prioritization --I want these mixed objects to look as much as possible like all the other objects in the system. Here's a bit of working (at least to a first approximation) code:
#!/usr/bin/perl -w use strict; package Object; sub new { return bless {}, shift; } sub speak { print shift() . " says hello\n"; }; package mixin_simple; sub yell { print shift() . " says HELLO\n"; } package mixin_shadow; sub speak { print shift() . " whispers hello\n"; }; package main; sub mix { my ( $object, @classes ) = @_; push @classes, ref $object; my $package_name = join ( '_', @classes ); my $package_list = join ( ' ', @classes ); my $declaration = "package $package_name; use base qw($package_list);"; eval $declaration; die "declaration error: $@\n" if $@; return bless $object, $package_name; } my $obj = Object->new; $obj->speak; mix ( $obj, 'mixin_simple' ); $obj->speak; $obj->yell; print "object is still an Object\n" if $obj->isa('Object'); my $whisperer = Object->new; mix ( $whisperer, 'mixin_shadow' ); $whisperer->speak; print "whisperer is still an Object\n" if $whispererpk->isa('Object' +);
This is just straight multiple inheritance, but we've created a container package so that the composition can happen on the fly. There's at least one more bit of complexity that needs to be added, though. I'm going to be creating a lot of each kind of these objects, so we should probably be caching the package creation.
sub mix { my ( $object, @classes ) = @_; push @classes, ref $object; my $package_name = join ( '_', @classes ); my $package_list = join ( ' ', @classes ); my $check = "package $package_name; \$declared"; unless ( eval $check ) { print " -- creating new package $package_name -- \n"; my $declaration = "package $package_name; use base qw($package_list); use vars '\$declared'; \$declared++"; eval $declaration; die "declaration error: $@\n" if $@; }; bless $object, $package_name; }
So I lied: it's 16 lines, not 10. Dave Rolsky actually does the caching a much cleaner way, but I've posted what I wrote before I found his code.
A bit of context might help to explain why this is a useful thing to be able to do. In XML::Comma, document structures are made up of different kinds of elements. There are various ways to extend and configure elements (macros, includes, references to other element definitions in the system), but it would also be useful -- for tmtowtdoi, among other, reasons -- to be able to write module-level mixin classes to alter element behavior. Definitions could then look something like this:
<blob_element> <name>picture</name> <mixin>XML::Comma::Mixin::Standard_Image</mixin> <mixin>XML::Comma::Mixin::Auto_Versioning</mixin> </blob_element>
I'm interested in what other monks have to say about this scheme. I'll further encourage discussion by listing some of my thoughts and questions, in no particular order ...
Michael Schwern's mixin module uses a more sophisticated strategy than the simple base-concatenating that I've done here. He walks the symbol table of the mixin classes, globbing CODE references into the use'ing class. This is quite nice, and lets him implement an extra bit of structure that I have mixed feelings about: private methods (those with names prefixed by an "_"), are not mixed. But I've always been enamored of perl's laissez-faire access rules. If you want to call a private method of your "parent" class, that's okay with me -- you presumably know that you're doing something not-quite-kosher.
The other mixin-ism that I'm not comfortable with is the requirement that mixin and mixed classes must share a common base class. What does this buy you?
Why, as mentioned above, do I think about what I'm doing here as using mixin classes, rather than decorators? Because a decorator framework generally implies that decorators need to be aware that that's what they are, and adjust their assumptions and calling practices accordingly. In particular, decorators often redispatch method calls up (or across, really) the object chain, and decorator frameworks require that you do this in some particular way (you can't just use SUPER).
Again, maybe this is what I ought to want, but at the moment anyway, I don't. (More news at eleven.) XML::Comma has hooks that accomplish much the same thing as redispatch-oriented decorators. (And when you type "hook," you feel like you're floating along in lisp-land, not in C++-hell. Much more cozy. Or maybe that's just me.)
Kwindla
|
|---|
| Replies are listed 'Best First'. | |
|---|---|
|
Re: very simple per-object mixins
by perrin (Chancellor) on Nov 05, 2003 at 18:07 UTC | |
by tilly (Archbishop) on Nov 05, 2003 at 21:22 UTC | |
|
Re: very simple per-object mixins
by autarch (Hermit) on Nov 07, 2003 at 16:19 UTC | |
by khkramer (Scribe) on Nov 07, 2003 at 17:06 UTC |