Class::MixinFactory::ReadMe - About the Mixin Class Factory
package MyClass;
use Class::MixinFactory -hasafactory;
sub new { ... }
sub foo { return "Foo Bar" }
package MyClass::Logging;
sub foo { warn "Calling foo"; (shift)->NEXT('foo', @_) }
package MyClass::UpperCase;
sub foo { uc( (shift)->NEXT('foo', @_) ) }
package main;
my $class = MyClass->class( 'Logging', 'UpperCase' );
print $class->new()->foo();
# Calls MyClass::Logging::foo, MyClass::UpperCase::foo, MyClass::foo
This distribution facilitates the run-time generation of classes which inherit
from a base class and some optional selection of mixin classes.
A factory is provided to generate the mixed classes with multiple inheritance. A
NEXT method allows method redispatch up the inheritance chain.
When developing an object class that will be used by different people for
different purposes, I find myself drawn to solutions in which a minimal base
class provides the shared behavior they all need, and a collection of
subclasses provides layers of additional functionality.
For example, consider a text templating framework, which might be separated into
several elements:
- •
- a base class, which provides methods to convert marked-up
text into runnable code,
- •
- an extension which enhances security by running the code in
a Safe compartment,
- •
- an extension which feeds output through an HTML-escaping
filter, and
- •
- an extension which records internal profiling data for
benchmarking purposes.
(See Text::MicroMason for an example of this design.)
A naive implementation of this might use a subclass for each behaviour, and look
like the following:
+---------+
| Base |
+---------+
|
+-----------------+-----------------+
v v v
+---------+ +---------+ +---------+
|Benchmark| | Filter | | Safe | @ISA=qw(Base)
+---------+ +---------+ +---------+
The well-known problem with this implementation appears when you want to combine
several features:
+---------+
| Base |
+---------+
|
+-----------------+-----------------+
v v v
+---------+ +---------+ +---------+
|Benchmark| | Filter | | Safe | @ISA=qw(Base)
+---------+ +---------+ +---------+
| |
+--------+--------+
v
+-------------+
| Safe_Filter | @ISA=qw(Filter Safe)
+-------------+
This is the dreaded "diamond inheritance" problem: if Base provides a
compile() method, which Filter and Safe each override to perform
additional actions before or after calling
SUPER::compile(), how can we
ensure they are all called in the correct sequence?
The standard software engineering solution is to replace the use of inheritance
with decomposition into several different classes of objects, which then
cooperate through decoration and delegation; for example, using separate
classes for a resolver, a lexer, a parser, a compiler, and an output channel.
(See HTML::Mason for an example of this design.)
Indeed, composition is an underutilized design technique, and there are many
times when inheritance is not the best tool to use. But of course, in Perl
there's more than one way to solve this problem, one of which is facilitated
by this distribution.
We can rearrange our class hierarchy to avoid diamond inheritance by using a
base and a collection of mixin classes, which don't directly inherit from the
base class:
+---------+ +---------+ +---------+ +---------+
|Benchmark| | Filter | | Safe | | Base |
+---------+ +---------+ +---------+ +---------+
| | |
+-----------------+-----------------+
v
+-------------+
| Safe_Filter | @ISA=qw(Filter
+-------------+ Safe Base)
However, in this condition our mixin classes can't call SUPER methods at all!
Instead, another redispatch mechanism is needed, one that is able to
back-track through the inheritance tree and explore other branches. (See NEXT
for such an implementation.)
The order in which mixins are stacked is significant, so the caller does need to
have some understanding of how their behaviors interact. For example, you'd
typically want to ensure that the Benchmarking mixin was the first in the
chain, so that it could time everything later in the sequence.
The Class::MixinFactory distribution provides several elements to facilitate
this kind of dynamic mixin architecture. The top level package is just a
facade that loads the other necessary classes and provides a few import
options for compile-time convenience. (See Class::MixinFactory.)
To generate an object with some combination of mixins, you first use a mixin
factory to generate a mixed class. If a class with that combination of classes
has already been created, it is reused. You can add a factory method to your
base class, create a separate factory object, or inherit to produce a factory
class. (See Class::MixinFactory::Factory.)
To allow mixin classes to redispatch to subsequent classes, all mixed classes
also inherit from a class which provides a
NEXT() method. (If you would
prefer, your mixin class can alternately use the AUTOLOAD solution provided by
the NEXT::ACTUAL module from CPAN, or any other equivalent re-dispatch
mechanism.) (See Class::MixinFactory::NEXT.)
There are number of other modules on CPAN that also support mixins, method
importing, or run-time multiple inheritance, while others don't use mixins but
are addressing a similar area of concern.
- •
- The mixin, Class::Mixin, and Spiffy modules support mixin
classes but don't have a configurable factory object or support run-time
mixin selection.
- •
- The Class::Mix and Class::Mutator modules provide run-time
class generation with multiple inheritance, but don't provide a
configurable factory object or a redispatch technique.
- •
- The Class::Factory module has a factory interface, but
doesn't support multiple inheritance.
- •
- The NEXT module provides a backtracking equivalent to SUPER
similar to the NEXT method included here, but uses AUTOLOAD rather than a
universal method.
- •
- The Class::Delegate and other modules support decoration to
address this problem via decomposition.
- •
- The Class::Role, Class::Roles and Class::Trait modules
support composing shared behaviors into your class.
This is version 0.92.
Elements of the interface remain open to change.
This module is new and relatively untested.
Please report any problems you encounter to the author at the below address.
This module should work with any version of Perl 5, without platform
dependencies or additional modules beyond the core distribution.
You should be able to install this module using the CPAN shell interface:
perl -MCPAN -e 'install Class::MixinFactory'
Alternately, you may retrieve this package from CPAN
("
http://search.cpan.org/~evo/") or from the author's site
("
http://www.evoscript.org/Class-MixinFactory").
After downloading the distribution, follow the normal procedure to unpack and
install it, using the commands shown below or their local equivalents on your
system:
tar xzf Class-MixinFactory-*.tar.gz
cd Class-MixinFactory-*
perl Makefile.PL
make test && sudo make install
If you have questions or feedback about this module, please feel free to contact
the author at the below address. Although there is no formal support program,
I do attempt to answer email promptly.
I would be particularly interested in any suggestions towards improving the
documentation, correcting any Perl-version or platform dependencies, as well
as general feedback and suggested additions.
Bug reports that contain a failing test case are greatly appreciated, and
suggested patches will be promptly considered for inclusion in future
releases.
To report bugs via the CPAN web tracking system, go to
"
http://rt.cpan.org/NoAuth/Bugs.html?Dist=Class-MixinFactory" or
send mail to "Dist=Class-MixinFactory#rt.cpan.org", replacing
"#" with "@".
If you've found this module useful or have feedback about your experience with
it, consider sharing your opinion with other Perl users by posting your
comment to CPAN's ratings system
("
http://cpanratings.perl.org/rate/?distribution=Class-MixinFactory").
For more general discussion, you may wish to post a message on PerlMonks
("
http://perlmonks.org/?node=Seekers%20of%20Perl%20Wisdom") or on
the comp.lang.perl.misc newsgroup
("
http://groups.google.com/groups?group=comp.lang.perl.misc").
Developed by Matthew Simon Cavalletto at Evolution Softworks. You may contact
the author directly at "evo#cpan.org" or
"simonm#cavalletto.org", replacing "#" with "@".
Custom development and technical consulting are available at
"
www.evolutionsoftworks.com". More free Perl software is available
at "
www.evoscript.org".
My sincere thanks to the Perl Monks community for their feedback on earlier
versions of this commentary.
http://perlmonks.org/index.pl?node_id=398061
http://perlmonks.org/index.pl?node_id=399040
Copyright 2004 Matthew Simon Cavalletto.
You may use, modify, and distribute this software under the same terms as
Perl.