Class-MixinFactory-0.92004075500007650000120000000000001015245233500142575ustar00simonmadminClass-MixinFactory-0.92/CHANGES010064400007650000120000000007461015245204500153330ustar00simonmadminNAME Class::MixinFactory::Changes - Revision history for Class::MixinFactory. HISTORY Version 0.92 2004-11-16 Fixed namespace stash check to require missing classes when needed. 2004-11-16 Documentation tweaks. Version 0.91 2004-11-15 Packaging and documentation. Separated Factory and Facade classes. 2004-11-14 Tests and additional interface and documentation work. 2004-11-13 Created. SEE ALSO the Class::MixinFactory::ReadMe manpage Class-MixinFactory-0.92/Makefile.PL010064400007650000120000000021221015245223400163000ustar00simonmadminuse ExtUtils::MakeMaker; ######################################################################## WriteMakefile( 'NAME' => 'Class::MixinFactory', 'VERSION_FROM' => 'MixinFactory.pm', 'PREREQ_PM' => {}, ($] >= 5.005 ? ( # ABSTRACT_FROM => 'MixinFactory/ReadMe.pod', AUTHOR => 'Matthew Simon Cavalletto ', ) : ()), ); ######################################################################## sub MY::postamble { q{ cleandist: FORCE make again; make cleanmanifest; make docs; make dist again: FORCE make clean; perl Makefile.PL; make pm_to_blib cleanmanifest: realclean FORCE rm MANIFEST ; perl Makefile.PL; touch MANIFEST; make manifest %.t: pm_to_blib FORCE make; perl -Iblib/lib $@ htmldoc: pm_to_blib FORCE perl make_extras/htmldoc.pl $(MAN3PODS) cover: FORCE cover -delete; HARNESS_PERL_SWITCHES=-MDevel::Cover make test; cover docs : README CHANGES README: MixinFactory/ReadMe.pod pod2text MixinFactory/ReadMe.pod > README CHANGES: MixinFactory/Changes.pod pod2text MixinFactory/Changes.pod > CHANGES }; } Class-MixinFactory-0.92/MANIFEST010064400007650000120000000006631014647162500154770ustar00simonmadminCHANGES Makefile.PL MANIFEST MANIFEST.SKIP MixinFactory.pm MixinFactory/Changes.pod MixinFactory/Factory.pm MixinFactory/HasAFactory.pm MixinFactory/InsideOutAttr.pm MixinFactory/NEXT.pm MixinFactory/ReadMe.pod README t/factory_class.t t/factory_method.t t/factory_object.t t/isa_subclass.t t/prefix_class.t t/prefix_method.t t/prefix_object.t t/version.t META.yml Module meta-data (added by MakeMaker) Class-MixinFactory-0.92/MANIFEST.SKIP010064400007650000120000000003011014647160200162240ustar00simonmadmin(^|/)\. ^MANIFEST\.bak ^Makefile$ ^Makefile\.old ^blib/ \.tar\.gz$ ^pm_to_blib dbix_sqlengine_seq test_data htmldoc/ cover_db DBIx-SQLEngine-[^/]+/ sqle_test (^|/)\d{3} test.config$ make_extrasClass-MixinFactory-0.92/META.yml010064400007650000120000000004661015245233500156120ustar00simonmadmin# http://module-build.sourceforge.net/META-spec.html #XXXXXXX This is a prototype!!! It will change in the future!!! XXXXX# name: Class-MixinFactory version: 0.92 version_from: MixinFactory.pm installdirs: site requires: distribution_type: module generated_by: ExtUtils::MakeMaker version 6.21 Class-MixinFactory-0.92/MixinFactory004075500007650000120000000000001015245233500166735ustar00simonmadminClass-MixinFactory-0.92/MixinFactory/Changes.pod010064400007650000120000000007441014647447700210460ustar00simonmadmin=head1 NAME Class::MixinFactory::Changes - Revision history for Class::MixinFactory. =head1 HISTORY =head2 Version 0.92 2004-11-16 Fixed namespace stash check to require missing classes when needed. 2004-11-16 Documentation tweaks. =head2 Version 0.91 2004-11-15 Packaging and documentation. Separated Factory and Facade classes. 2004-11-14 Tests and additional interface and documentation work. 2004-11-13 Created. =head1 SEE ALSO L =cut Class-MixinFactory-0.92/MixinFactory/Factory.pm010064400007650000120000000071311014647434700207300ustar00simonmadminpackage Class::MixinFactory::Factory; $VERSION = 0.91; ######################################################################## use strict; use Carp (); ######################################################################## use Class::MixinFactory::InsideOutAttr qw(base_class mixin_prefix mixed_prefix); use Class::MixinFactory::NEXT; sub next_dispatch_class { 'Class::MixinFactory::NEXT' } ######################################################################## sub new { my $sym; my $self = bless \$sym, shift; while ( my $method = shift @_ ) { $self->$method( shift ); } $self; } ######################################################################## sub class { my $factory = shift; my @mixins = ( @_ == 1 and ref($_[0]) ) ? @{ $_[0] } : @_; my $base_class = $factory->base_class(); my $mixin_prefix = $factory->mixin_prefix() || $base_class || ''; my $mixed_prefix = $factory->mixed_prefix() || ( $base_class ? $base_class : ref($factory) || $factory ) . "::AUTO"; my @classes = map { ( $_ =~ /::/ ) ? $_ : $mixin_prefix ? $mixin_prefix . '::' . $_ : $_ } @mixins; my $label = join '_', map { s/^\Q$mixin_prefix\E:://; s/:://g; $_ } map "$_", @classes; my $new_class = $mixed_prefix . "::" . ( $label || "Base" ); return $new_class if do { no strict 'refs'; @{ "$new_class\::ISA" } }; my @isa = ( @classes, $base_class, $factory->next_dispatch_class ); foreach my $package ( @classes ) { next if do { no strict 'refs'; scalar keys %{ $package . '::' } }; my $filename = "$package.pm"; $filename =~ s{::}{/}g; # warn "require $filename"; require $filename; } { no strict; @{ "$new_class\::ISA" } = @isa; } $new_class; } ######################################################################## 1; __END__ =head1 NAME Class::MixinFactory::Factory - Class Factory with Selection of Mixins =head1 SYNOPSIS use Class::MixinFactory::Factory; my $factory = Class::MixinFactory::Factory->new(); $factory->base_class( "MyClass"); $factory->mixin_prefix( "MyMixins" ); $factory->mixed_prefix( "MyClasses" ); my $class = $factory->class( @mixins ); =head1 DESCRIPTION A mixin factory generates new classes at run-time which inherit from each of several classes. =head1 PUBLIC METHODS =over 4 =item new() $factory_class->new() : $factory $factory_class->new( %attributes ) : $factory Create a new factory object. May be passed a hash of attributes, with the key matching one of the supported accessor methods named below and the value containing the value to assign. =item base_class() $factory->base_class() : $package_name $factory->base_class( $package_name ) Required. Get or set the base class to be inherited from by all mixed classes. =item mixin_prefix() $factory->mixin_prefix() : $package_name $factory->mixin_prefix( $package_name ) Optional. Get or set a prefix to be placed before all mixin class names that don't contain a double-colon. Defaults to the name of the base class. =item mixed_prefix() $factory->mixed_prefix() : $package_name $factory->mixed_prefix( $package_name ) Optional. Get or set a prefix to be placed before all generated class names. Defaults to the name of the base class or the factory class followed by "::AUTO" =item class() $factory->class( @mixins ) : $package_name Find or generate a class combining the requested mixin classes. =back =head1 SEE ALSO For a facade interface that facilitates access to this functionality, see L. For distribution, installation, support, copyright and license information, see L. =cut Class-MixinFactory-0.92/MixinFactory/HasAFactory.pm010064400007650000120000000036471014624157000214630ustar00simonmadminpackage Class::MixinFactory::HasAFactory; use Class::MixinFactory::NEXT; @ISA = 'Class::MixinFactory::NEXT'; use strict; ######################################################################## use Class::MixinFactory::InsideOutAttr 'mixin_factory_ref'; sub mixin_factory { my $self = shift; my $base_class = ref($self) || $self; $base_class->mixin_factory_ref() or $base_class->mixin_factory_ref( Class::MixinFactory::Factory->new( base_class => $base_class ) ) } sub class { (shift)->mixin_factory()->class( @_ ) } ######################################################################## 1; __END__ =head1 NAME Class::MixinFactory::HasAFactory - Delegates to a Factory =head1 SYNOPSIS package My::BaseClass; @ISA = 'Class::MixinFactory::HasAFactory'; sub new { ... } sub foo { return "Foo Bar" } package My::Logging; sub foo { warn "Calling foo"; (shift)->NEXT('foo', @_) } package My::UpperCase; sub foo { uc( (shift)->NEXT('foo', @_) ) } package main; use My::BaseClass; print My::BaseClass->class()->new()->foo(); print My::BaseClass->class( 'Logging' )->new()->foo(); print My::BaseClass->class( 'UpperCase' )->new()->foo(); print My::BaseClass->class( 'Logging', 'UpperCase' )->new()->foo(); =head1 DESCRIPTION A class for use by classes which want a factory method. Inherit from this class to obtain the class() factory method, described below. =head1 PUBLIC METHODS =over 4 =item mixin_factory() BaseClass->mixin_factory() : $factory_object Gets the associated mixin factory. Generated the first time it is needed. =item class() BaseClass->class( @mixins ) : $package_name Calls the class() method on the associated mixin factory. =back =head1 SEE ALSO For a facade interface that facilitates access to this functionality, see L. For distribution, installation, support, copyright and license information, see L. =cut Class-MixinFactory-0.92/MixinFactory/InsideOutAttr.pm010064400007650000120000000044551014623370100220500ustar00simonmadminpackage Class::MixinFactory::InsideOutAttr; use strict; ######################################################################## sub import { my $methodmaker = shift; my $target_class = ( caller )[0]; no strict 'refs'; foreach my $attr ( @_ ) { *{ $target_class . '::' . $attr } = sub { inside_out( $attr, @_ ) } } defined *{ $target_class . '::' . 'DESTROY' }{CODE} or *{ $target_class . '::' . 'DESTROY' } = \&destroy; } my %inside_out; sub inside_out { my $callee = shift; my $key = shift; if ( $key eq 'DESTROY' ) { delete $inside_out{ $callee }; } elsif ( ! scalar @_ ) { $inside_out{ $callee }{ $key } } else { $inside_out{ $callee }{ $key } = shift; } } sub destroy { my $callee = shift; delete $inside_out{ $callee }; } ######################################################################## 1; __END__ =head1 NAME Class::MixinFactory::InsideOutAttr - Method maker for inside out data =head1 SYNOPSIS package My::Class; use Class::MixinFactory::InsideOutAttr qw( foo bar baz ); sub new { ... } package main; My::Class->foo( 'Foozle' ); print My::Class->foo(); my $object = My::Class->new(); $object->foo( 'Bolix' ); print $object->foo(); =head1 DESCRIPTION A simple method maker with opaque data storage. =head2 Usage To generate accessor methods for your class, use this package and pass the desired method names to the use or import statement. Generates get/set accessor methods which can store values for a class or its instances. Each method stores the values associated with various objects in an hash keyed by the object's stringified identity. =head2 Destruction A DESTROY method is installed to remove data for expired objects from the storage hash. (If the DESTROY method is not called, your program will not release this data and memory will be wasted.) If you implement your own DESTROY method, it should also call C. =head1 SEE ALSO This class is used internally by L. This is similar to the functionality provided by Class::MakeMethods::Template::InsideOut; for a more generalized approach to this issue see L. For distribution, installation, support, copyright and license information, see L. =cut Class-MixinFactory-0.92/MixinFactory/NEXT.pm010064400007650000120000000041241014623406600200660ustar00simonmadminpackage Class::MixinFactory::NEXT; use strict; ######################################################################## sub NEXT { my ( $self, $method, @args ) = @_; my $package = caller(); my @classes = ref($self) || $self; my $found_current = 0; while ( my $class = shift @classes ) { if ( $class eq $package ) { $found_current = 1 } elsif ( $found_current and my $sub = $class->can( $method ) ) { return &$sub( $self, @args ); } no strict; unshift @classes, @{ $class . "::ISA" }; } Carp::croak( "Can't find NEXT method for $method" ); } ######################################################################## 1; __END__ =head1 NAME Class::MixinFactory::NEXT - Superclass method redispatch for mixins =head1 SYNOPSIS use Class::MixinFactory::NEXT; package My::BaseClass; sub foo { return "Foo Bar" } package My::Logging; sub foo { warn "Calling foo"; (shift)->NEXT('foo', @_) } package My::MixedClass; @ISA = ( 'My::Logging', 'My::BaseClass', 'Class::MixinFactory::NEXT'; ); package main; print My::MixedClass->foo(); =head1 DESCRIPTION Enhanced superclass method dispatch for use inside mixin class methods. Allows mixin classes to redispatch to other classes in the inheritance tree without themselves inheriting from anything. =head2 Public Methods This package defines one method, named NEXT. $callee->NEXT( $method, @args ); Searches the inheritance tree of the callee until it finds the package from which NEXT is being called, and then continues searching until the next class which can perform the named method. Unlike SUPER, this method will backtrack down the inheritance tree to find implementations later in the search path even if they are on a separate branch. =head1 SEE ALSO This class is automatically included by L. This is similar to the functionality provided by NEXT::ACTUAL, but without using AUTOLOAD; for a more generalized approach to this issue see L. For distribution, installation, support, copyright and license information, see L. =cut Class-MixinFactory-0.92/MixinFactory/ReadMe.pod010064400007650000120000000233071014723621000206110ustar00simonmadmin=head1 NAME Class::MixinFactory::ReadMe - About the Mixin Class Factory =head1 SYNOPSIS 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 =head1 ABSTRACT 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. =head1 MOTIVATION =head2 The Challenge 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: =over 4 =item * a base class, which provides methods to convert marked-up text into runnable code, =item * an extension which enhances security by runing the code in a Safe compartment, =item * an extension which feeds output through an HTML-escaping filter, and =item * an extension which records internal profiling data for benchmarking purposes. =back (See Text::MicroMason for an example of this design.) =head2 A Bad Approach 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? =head2 A Good Approach 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. =head2 A Different Approach 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 L 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. =head2 This Distribution The Class::MixinFactory distribution provides serveral elements to facilitate tihs 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 L.) 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 L.) 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 L.) =head1 RELATED MODULES 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. =over 4 =item * The L, L, and L modules support mixin classes but don't have a configurable factory object or support run-time mixin selection. =item * The L and L modules provide run-time class generation with multiple inheritance, but don't provide a configurable factory object or a redispatch technique. =item * The L module has a factory interface, but doesn't support multiple inheritance. =item * The L module provides a backtracking equivalent to SUPER similar to the NEXT method included here, but uses AUTOLOAD rather than a universal method. =item * The L and other modules support decoration to address this problem via decomposition. =item * The L, L and L modules support composing shared behaviors into your class. =back =head1 VERSION This is version 0.92. Elements of the interface remain open to change. =head1 BUGS This module is new and relatively untested. Please report any problems you encounter to the author at the below address. =head1 INSTALLATION 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 (C) or from the author's site (C). 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 =head1 SUPPORT 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 C or send mail to C, replacing C<#> with C<@>. 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 (C). For more general discussion, you may wish to post a message on PerlMonks (C) or on the comp.lang.perl.misc newsgroup (C). =head1 AUTHOR Developed by Matthew Simon Cavalletto at Evolution Softworks. You may contact the author directly at C or C, replacing C<#> with C<@>. Custom development and technical consulting are available at C. More free Perl software is available at C. =head1 THANKS 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 =head1 LICENSE Copyright 2004 Matthew Simon Cavalletto. You may use, modify, and distribute this software under the same terms as Perl. =cut Class-MixinFactory-0.92/MixinFactory.pm010064400007650000120000000132571015245207300173140ustar00simonmadminpackage Class::MixinFactory; $VERSION = 0.92; use strict; ######################################################################## use Class::MixinFactory::Factory; sub base_factory_class { 'Class::MixinFactory::Factory' } use Class::MixinFactory::HasAFactory; sub hasa_factory_class { 'Class::MixinFactory::HasAFactory' } ######################################################################## sub import { my ( $facade, $import, @args ) = @_; return unless $import; my $target_class = ( caller )[0]; no strict 'refs'; if ( $import eq '-isafactory' ) { push @{"$target_class\::ISA"}, $facade->base_factory_class; } elsif ( $import eq '-hasafactory' ) { push @{"$target_class\::ISA"}, $facade->hasa_factory_class(); } elsif ( $import eq '-isasubclass' ) { push @{"$target_class\::ISA"}, (shift @args)->class( @args ); } else { require Exporter; goto &Exporter::import } } ######################################################################## sub new { (shift)->base_factory_class->new( @_ ) } ######################################################################## 1; __END__ =head1 NAME Class::MixinFactory - Class Factory with Selection of Mixins =head1 SYNOPSIS 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 =head1 DESCRIPTION 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. =head1 USAGE The Class::MixinFactory package is just a facade that loads the necessary classes and provides a few import options for compile-time convenience. =head2 Factory Interface To generate an object with some combination of mixins, you first pass the names of the mixin classes to a class factory which will generate a mixed class. (Or return the name of the already generated class, if there has been a previous request with the same combination of mixins.) You can add a factory method to your base class, create a separate factory object, or inherit to produce a factory class. =over 4 =item Factory Method To add a factory method to a base class, inherit from the Class::MixinFactory::HasAFactory class, or use the C<-hasafactory> import option: package MyClass; use Class::MixinFactory -hasafactory; package main; my $class = MyClass->class( 'Logging', 'UpperCase' ); print $class->new()->foo(); =item Factory Class To create a new class which will act as a factory for another base class, inherit from the Class::MixinFactory::Factory class, or use the C<-isafactory> import option: package MyClass::Factory; use Class::MixinFactory -isafactory; MyClass::Factory->base_class( "MyClass" ); package main; my $class = MyClass::Factory->class( 'Logging', 'UpperCase' ); print $class->new()->foo(); =item Factory Object To create an object which will act as a factory, create a Class::MixinFactory::Factory instance by calling the new() method: use Class::MixinFactory; my $factory = Class::MixinFactory->new(); $factory->base_class( "MyClass" ); my $class = $factory->class( 'Logging', 'UpperCase' ); print $class->new()->foo(); =back =head2 Inheriting from a Mixed Class =over 4 =item Inheriting with a Factory Method or Factory Object A subclass can inherit from a mixed class: package MyClass::CustomWidget; @ISA = MyClass->class( 'Logging', 'UpperCase' ); sub foo { local $_ = (shift)->NEXT('foo', @_); tr[a-z][z-a]; $_ } package main; print MyClass::CustomWidget->new()->foo(); =item Inheriting with a Factory Class A subclass can use a factory class to define its own inheritance: package MyClass::CustomWidget; use Class::MixinFactory -isasubclass, MyClass::Factory => 'Logging', 'UpperCase'; sub foo { local $_ = (shift)->NEXT('foo', @_); tr[a-z][z-a]; $_ } package main; print MyClass::CustomWidget->new()->foo(); =back =head2 Configuring a Factory Factories support methods that control which classes they will use. The base class will be inherited from by all mixed classes. $factory->base_class( "HelloWorld" ); The mixin prefix is prepended to the mixin names passed to the class() method. Mixin names that contain a "::" are assumed to be fully qualified and are not changed. If empty, the base_class is used. $factory->mixin_prefix( 'HelloFeature' ); The mixed prefix is at the start of all generated class names. If empty, the base_class is used, or the factory's class name. $factory->mixed_prefix( 'HelloClass' ); =head2 Writing a Mixin Class Writing a mixin class is almost the same as writing a subclass, except where methods need to redispatch to the base-class implementation. (The SUPER::method syntax will only search for classes that the mixin itself inherits from; to search back up the inheritance tree and explore other branches, another redispatch mechanism is needed.) A method named NEXT is provided to continue the search through to the next class which provides a given method. The order in which mixins are stacked is significant, so the caller should understand how their behaviors interact. (See L.) =head1 SEE ALSO For distribution, installation, support, copyright and license information, see L. =cut Class-MixinFactory-0.92/README010064400007650000120000000250071015245204400152140ustar00simonmadminNAME Class::MixinFactory::ReadMe - About the Mixin Class Factory SYNOPSIS 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 ABSTRACT 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. MOTIVATION The Challenge 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 runing 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 Bad Approach 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? A Good Approach 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. A Different Approach 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 the NEXT manpage 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. This Distribution The Class::MixinFactory distribution provides serveral elements to facilitate tihs 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 the Class::MixinFactory manpage.) 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 the Class::MixinFactory::Factory manpage.) 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 the Class::MixinFactory::NEXT manpage.) RELATED MODULES 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. VERSION This is version 0.92. Elements of the interface remain open to change. BUGS This module is new and relatively untested. Please report any problems you encounter to the author at the below address. INSTALLATION 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 SUPPORT 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"). AUTHOR 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". THANKS 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 LICENSE Copyright 2004 Matthew Simon Cavalletto. You may use, modify, and distribute this software under the same terms as Perl. Class-MixinFactory-0.92/t004075500007650000120000000000001015245233500145225ustar00simonmadminClass-MixinFactory-0.92/t/factory_class.t010064400007650000120000000027301014573622200176230ustar00simonmadminuse Test; BEGIN { plan tests => 12 } { package HelloWorld; sub hello { return "Hello World!" } package HelloWorld::UpperCase; sub hello { uc( (shift)->NEXT('hello', @_) ) } package HelloWorld::Bold; sub hello { "" . (shift)->NEXT('hello', @_) . "" } package HelloWorld::Italic; sub hello { "" . (shift)->NEXT('hello', @_) . "" } package HelloWorld::Factory; use Class::MixinFactory -isafactory; HelloWorld::Factory->base_class( "HelloWorld" ); } package main; ok( HelloWorld->hello(), 'Hello World!' ); ok( HelloWorld::Factory->class()->hello(), 'Hello World!' ); ok( HelloWorld::Factory->class( 'Bold' )->hello(), 'Hello World!' ); ok( HelloWorld::Factory->class( 'Italic' )->hello(), 'Hello World!' ); ok( HelloWorld::Factory->class( 'UpperCase' )->hello(), 'HELLO WORLD!' ); ok( HelloWorld::Factory->class( 'Bold', 'Italic' )->hello(), 'Hello World!' ); ok( HelloWorld::Factory->class( 'Italic', 'Bold' )->hello(), 'Hello World!' ); ok( HelloWorld::Factory->class( 'Bold', 'UpperCase' )->hello(), 'HELLO WORLD!' ); ok( HelloWorld::Factory->class( 'UpperCase', 'Bold' )->hello(), 'HELLO WORLD!' ); ok( HelloWorld::Factory->class( 'Italic', 'UpperCase' )->hello(), 'HELLO WORLD!'); ok( HelloWorld::Factory->class( 'UpperCase', 'Italic' )->hello(), 'HELLO WORLD!'); ok( HelloWorld::Factory->class( 'UpperCase', 'Bold', 'Italic' )->hello(), 'HELLO WORLD!'); Class-MixinFactory-0.92/t/factory_method.t010064400007650000120000000024441014573630100177760ustar00simonmadminuse Test; BEGIN { plan tests => 12 } { package HelloWorld; use Class::MixinFactory -hasafactory; sub hello { return "Hello World!" } package HelloWorld::UpperCase; sub hello { uc( (shift)->NEXT('hello', @_) ) } package HelloWorld::Bold; sub hello { "" . (shift)->NEXT('hello', @_) . "" } package HelloWorld::Italic; sub hello { "" . (shift)->NEXT('hello', @_) . "" } } package main; ok( HelloWorld->hello(), 'Hello World!' ); ok( HelloWorld->class()->hello(), 'Hello World!' ); ok( HelloWorld->class( 'Bold' )->hello(), 'Hello World!' ); ok( HelloWorld->class( 'Italic' )->hello(), 'Hello World!' ); ok( HelloWorld->class( 'UpperCase' )->hello(), 'HELLO WORLD!' ); ok( HelloWorld->class( 'Bold', 'Italic' )->hello(), 'Hello World!' ); ok( HelloWorld->class( 'Italic', 'Bold' )->hello(), 'Hello World!' ); ok( HelloWorld->class( 'Bold', 'UpperCase' )->hello(), 'HELLO WORLD!' ); ok( HelloWorld->class( 'UpperCase', 'Bold' )->hello(), 'HELLO WORLD!' ); ok( HelloWorld->class( 'Italic', 'UpperCase' )->hello(), 'HELLO WORLD!'); ok( HelloWorld->class( 'UpperCase', 'Italic' )->hello(), 'HELLO WORLD!'); ok( HelloWorld->class( 'UpperCase', 'Bold', 'Italic' )->hello(), 'HELLO WORLD!'); Class-MixinFactory-0.92/t/factory_object.t010064400007650000120000000025171014573630100177650ustar00simonmadminuse Test; BEGIN { plan tests => 12 } { package HelloWorld; sub hello { return "Hello World!" } package HelloWorld::UpperCase; sub hello { uc( (shift)->NEXT('hello', @_) ) } package HelloWorld::Bold; sub hello { "" . (shift)->NEXT('hello', @_) . "" } package HelloWorld::Italic; sub hello { "" . (shift)->NEXT('hello', @_) . "" } } package main; use Class::MixinFactory; my $factory = Class::MixinFactory->new(); $factory->base_class( "HelloWorld" ); ok( HelloWorld->hello(), 'Hello World!' ); ok( $factory->class()->hello(), 'Hello World!' ); ok( $factory->class( 'Bold' )->hello(), 'Hello World!' ); ok( $factory->class( 'Italic' )->hello(), 'Hello World!' ); ok( $factory->class( 'UpperCase' )->hello(), 'HELLO WORLD!' ); ok( $factory->class( 'Bold', 'Italic' )->hello(), 'Hello World!' ); ok( $factory->class( 'Italic', 'Bold' )->hello(), 'Hello World!' ); ok( $factory->class( 'Bold', 'UpperCase' )->hello(), 'HELLO WORLD!' ); ok( $factory->class( 'UpperCase', 'Bold' )->hello(), 'HELLO WORLD!' ); ok( $factory->class( 'Italic', 'UpperCase' )->hello(), 'HELLO WORLD!'); ok( $factory->class( 'UpperCase', 'Italic' )->hello(), 'HELLO WORLD!'); ok( $factory->class( 'UpperCase', 'Bold', 'Italic' )->hello(), 'HELLO WORLD!'); Class-MixinFactory-0.92/t/isa_subclass.t010064400007650000120000000024251014621700000174300ustar00simonmadminuse Test; BEGIN { plan tests => 4 } { package HelloWorld; sub hello { return "Hello World!" } package HelloWorld::UpperCase; sub hello { uc( (shift)->NEXT('hello', @_) ) } package HelloWorld::Bold; sub hello { "" . (shift)->NEXT('hello', @_) . "" } package HelloWorld::Italic; sub hello { "" . (shift)->NEXT('hello', @_) . "" } package HelloWorld::Factory; use Class::MixinFactory -isafactory; BEGIN { $INC{"HelloWorld/Factory.pm"} = __FILE__ } BEGIN { HelloWorld::Factory->base_class( "HelloWorld" ) } } { package My::FirstStyle; use Class::MixinFactory -isasubclass => HelloWorld::Factory => 'UpperCase'; sub hello { "* " . (shift)->NEXT('hello', @_) . " *" } package My::SecondStyle; use Class::MixinFactory -isasubclass => HelloWorld::Factory => 'Bold', 'Italic'; sub hello { "* " . (shift)->NEXT('hello', @_) . " *" } package My::ThirdStyle; use Class::MixinFactory -isasubclass => HelloWorld::Factory => 'Bold', 'UpperCase'; sub hello { "* " . (shift)->NEXT('hello', @_) . " *" } } package main; ok( HelloWorld->hello(), 'Hello World!' ); ok( My::FirstStyle->hello(), '* HELLO WORLD! *' ); ok( My::SecondStyle->hello(), '* Hello World! *' ); ok( My::ThirdStyle->hello(), '* HELLO WORLD! *' ); Class-MixinFactory-0.92/t/prefix_class.t010064400007650000120000000034241014573773200174610ustar00simonmadminuse Test; BEGIN { plan tests => 15 } { package HelloWorld; sub hello { return "Hello World!" } package HelloFeature::UpperCase; sub hello { uc( (shift)->NEXT('hello', @_) ) } package HelloFeature::Bold; sub hello { "" . (shift)->NEXT('hello', @_) . "" } package HelloFeature::Italic; sub hello { "" . (shift)->NEXT('hello', @_) . "" } package HelloWorld::Factory; use Class::MixinFactory -isafactory; HelloWorld::Factory->base_class( "HelloWorld" ); HelloWorld::Factory->mixin_prefix( 'HelloFeature' ); HelloWorld::Factory->mixed_prefix( 'HelloClass' ); } package main; ok( HelloWorld->hello(), 'Hello World!' ); ok( HelloWorld::Factory->class()->hello(), 'Hello World!' ); ok( HelloWorld::Factory->class( 'Bold' )->hello(), 'Hello World!' ); ok( HelloWorld::Factory->class( 'Italic' )->hello(), 'Hello World!' ); ok( HelloWorld::Factory->class( 'UpperCase' )->hello(), 'HELLO WORLD!' ); ok( HelloWorld::Factory->class( 'Bold', 'Italic' )->hello(), 'Hello World!' ); ok( HelloWorld::Factory->class( 'Italic', 'Bold' )->hello(), 'Hello World!' ); ok( HelloWorld::Factory->class( 'Bold', 'UpperCase' )->hello(), 'HELLO WORLD!' ); ok( HelloWorld::Factory->class( 'UpperCase', 'Bold' )->hello(), 'HELLO WORLD!' ); ok( HelloWorld::Factory->class( 'Italic', 'UpperCase' )->hello(), 'HELLO WORLD!'); ok( HelloWorld::Factory->class( 'UpperCase', 'Italic' )->hello(), 'HELLO WORLD!'); ok( HelloWorld::Factory->class( 'UpperCase', 'Bold', 'Italic' )->hello(), 'HELLO WORLD!'); ok( HelloWorld::Factory->class(), 'HelloClass::Base' ); ok( HelloWorld::Factory->class( 'Bold' ), 'HelloClass::Bold' ); ok( HelloWorld::Factory->class( 'Bold', 'Italic' ), 'HelloClass::Bold_Italic' ); Class-MixinFactory-0.92/t/prefix_method.t010064400007650000120000000031201014573730000176140ustar00simonmadminuse Test; BEGIN { plan tests => 15 } { package HelloWorld; use Class::MixinFactory -hasafactory; HelloWorld->mixin_factory->mixin_prefix( 'HelloFeature' ); HelloWorld->mixin_factory->mixed_prefix( 'HelloClass' ); sub hello { return "Hello World!" } package HelloFeature::UpperCase; sub hello { uc( (shift)->NEXT('hello', @_) ) } package HelloFeature::Bold; sub hello { "" . (shift)->NEXT('hello', @_) . "" } package HelloFeature::Italic; sub hello { "" . (shift)->NEXT('hello', @_) . "" } } package main; ok( HelloWorld->hello(), 'Hello World!' ); ok( HelloWorld->class()->hello(), 'Hello World!' ); ok( HelloWorld->class( 'Bold' )->hello(), 'Hello World!' ); ok( HelloWorld->class( 'Italic' )->hello(), 'Hello World!' ); ok( HelloWorld->class( 'UpperCase' )->hello(), 'HELLO WORLD!' ); ok( HelloWorld->class( 'Bold', 'Italic' )->hello(), 'Hello World!' ); ok( HelloWorld->class( 'Italic', 'Bold' )->hello(), 'Hello World!' ); ok( HelloWorld->class( 'Bold', 'UpperCase' )->hello(), 'HELLO WORLD!' ); ok( HelloWorld->class( 'UpperCase', 'Bold' )->hello(), 'HELLO WORLD!' ); ok( HelloWorld->class( 'Italic', 'UpperCase' )->hello(), 'HELLO WORLD!'); ok( HelloWorld->class( 'UpperCase', 'Italic' )->hello(), 'HELLO WORLD!'); ok( HelloWorld->class( 'UpperCase', 'Bold', 'Italic' )->hello(), 'HELLO WORLD!'); ok( HelloWorld->class(), 'HelloClass::Base' ); ok( HelloWorld->class( 'Bold' ), 'HelloClass::Bold' ); ok( HelloWorld->class( 'Bold', 'Italic' ), 'HelloClass::Bold_Italic' ); Class-MixinFactory-0.92/t/prefix_object.t010064400007650000120000000031201014573766200176150ustar00simonmadminuse Test; BEGIN { plan tests => 15 } { package HelloWorld; sub hello { return "Hello World!" } package HelloFeature::UpperCase; sub hello { uc( (shift)->NEXT('hello', @_) ) } package HelloFeature::Bold; sub hello { "" . (shift)->NEXT('hello', @_) . "" } package HelloFeature::Italic; sub hello { "" . (shift)->NEXT('hello', @_) . "" } } package main; use Class::MixinFactory; my $factory = Class::MixinFactory->new(); $factory->base_class( "HelloWorld" ); $factory->mixin_prefix( 'HelloFeature' ); $factory->mixed_prefix( 'HelloClass' ); ok( HelloWorld->hello(), 'Hello World!' ); ok( $factory->class()->hello(), 'Hello World!' ); ok( $factory->class( 'Bold' )->hello(), 'Hello World!' ); ok( $factory->class( 'Italic' )->hello(), 'Hello World!' ); ok( $factory->class( 'UpperCase' )->hello(), 'HELLO WORLD!' ); ok( $factory->class( 'Bold', 'Italic' )->hello(), 'Hello World!' ); ok( $factory->class( 'Italic', 'Bold' )->hello(), 'Hello World!' ); ok( $factory->class( 'Bold', 'UpperCase' )->hello(), 'HELLO WORLD!' ); ok( $factory->class( 'UpperCase', 'Bold' )->hello(), 'HELLO WORLD!' ); ok( $factory->class( 'Italic', 'UpperCase' )->hello(), 'HELLO WORLD!'); ok( $factory->class( 'UpperCase', 'Italic' )->hello(), 'HELLO WORLD!'); ok( $factory->class( 'UpperCase', 'Bold', 'Italic' )->hello(), 'HELLO WORLD!'); ok( $factory->class(), 'HelloClass::Base' ); ok( $factory->class( 'Bold' ), 'HelloClass::Bold' ); ok( $factory->class( 'Bold', 'Italic' ), 'HelloClass::Bold_Italic' ); Class-MixinFactory-0.92/t/version.t010064400007650000120000000002741014561415400164540ustar00simonmadmin#!/usr/bin/perl use Test; BEGIN { plan tests => 3 } use Class::MixinFactory; ok( 1 ); eval "use Class::MixinFactory 0.001;"; ok( ! $@ ); eval "use Class::MixinFactory 2.0;"; ok( $@ );