Class-Container
view release on metacpan or search on metacpan
lib/Class/Container.pm view on Meta::CPAN
use strict;
package Class::Container;
{
$Class::Container::VERSION = '0.13';
}
my $HAVE_WEAKEN;
BEGIN {
eval {
require Scalar::Util;
Scalar::Util->import('weaken');
$HAVE_WEAKEN = 1;
};
*weaken = sub {} unless defined &weaken;
}
use Carp;
# The create_contained_objects() method lets one object
# (e.g. Compiler) transparently create another (e.g. Lexer) by passing
# creator parameters through to the created object.
#
# Any auto-created objects should be declared in a class's
# %CONTAINED_OBJECTS hash. The keys of this hash are objects which
# can be created and the values are the default classes to use.
# For instance, the key 'lexer' indicates that a 'lexer' parameter
# should be silently passed through, and a 'lexer_class' parameter
# will trigger the creation of an object whose class is specified by
# the value. If no value is present there, the value of 'lexer' in
# the %CONTAINED_OBJECTS hash is used. If no value is present there,
# no contained object is created.
#
# We return the list of parameters for the creator. If contained
# objects were auto-created, their creation parameters aren't included
# in the return value. This lets the creator be totally ignorant of
# the creation parameters of any objects it creates.
use Params::Validate qw(:all);
Params::Validate::validation_options( on_fail => sub { die @_ } );
my %VALID_PARAMS = ();
my %CONTAINED_OBJECTS = ();
my %VALID_CACHE = ();
my %CONTAINED_CACHE = ();
my %DECORATEES = ();
sub new
{
my $proto = shift;
my $class = ref($proto) || $proto;
my $self = bless scalar validate_with
(
params => $class->create_contained_objects(@_),
spec => $class->validation_spec,
called => "$class->new()",
), $class;
if ($HAVE_WEAKEN) {
my $c = $self->get_contained_object_spec;
foreach my $name (keys %$c) {
next if $c->{$name}{delayed};
$self->{$name}{container}{container} = $self;
weaken $self->{$name}{container}{container};
}
}
return $self;
}
sub all_specs
{
require B::Deparse;
my %out;
foreach my $class (sort keys %VALID_PARAMS)
{
my $params = $VALID_PARAMS{$class};
foreach my $name (sort keys %$params)
{
my $spec = $params->{$name};
lib/Class/Container.pm view on Meta::CPAN
# Figure out what class to use for this contained item
my $contained_class;
if ( exists $args->{"${name}_class"} ) {
$contained_class = $args->{"${name}_class"};
$p{"${name}_class"} = { type => SCALAR }; # Add to spec
} else {
$contained_class = $c->{$name}{class};
}
# We have to make sure it is loaded before we try calling allowed_params()
$class->_load_module($contained_class);
next unless $contained_class->can('allowed_params');
my $subparams = $contained_class->allowed_params($args);
foreach (keys %$subparams) {
$p{$_} ||= $subparams->{$_};
}
}
return \%p;
}
sub _iterate_ISA {
my ($class, $look_in, $cache_in, $add) = @_;
return $cache_in->{$class} if $cache_in->{$class};
my %out;
no strict 'refs'; ## no critic
foreach my $superclass (@{ "${class}::ISA" }) {
next unless $superclass->isa(__PACKAGE__);
my $superparams = $superclass->_iterate_ISA($look_in, $cache_in, $add);
@out{keys %$superparams} = values %$superparams;
}
if (my $x = $look_in->{$class}) {
@out{keys %$x} = values %$x;
}
@out{keys %$add} = values %$add if $add;
return $cache_in->{$class} = \%out;
}
sub get_contained_object_spec {
return (ref($_[0]) || $_[0])->_iterate_ISA(\%CONTAINED_OBJECTS, \%CONTAINED_CACHE);
}
sub validation_spec {
return (ref($_[0]) || $_[0])->_iterate_ISA(\%VALID_PARAMS, \%VALID_CACHE, { container => {type => HASHREF} });
}
1;
__END__
=head1 NAME
Class::Container - Glues object frameworks together transparently
=head1 VERSION
version 0.13
=head1 SYNOPSIS
package Car;
use Class::Container;
@ISA = qw(Class::Container);
__PACKAGE__->valid_params
(
paint => {default => 'burgundy'},
style => {default => 'coupe'},
windshield => {isa => 'Glass'},
radio => {isa => 'Audio::Device'},
);
__PACKAGE__->contained_objects
(
windshield => 'Glass::Shatterproof',
wheel => { class => 'Vehicle::Wheel',
delayed => 1 },
radio => 'Audio::MP3',
);
sub new {
my $package = shift;
# 'windshield' and 'radio' objects are created automatically by
# SUPER::new()
my $self = $package->SUPER::new(@_);
$self->{right_wheel} = $self->create_delayed_object('wheel');
... do any more initialization here ...
return $self;
}
=head1 DESCRIPTION
This class facilitates building frameworks of several classes that
inter-operate. It was first designed and built for C<HTML::Mason>, in
which the Compiler, Lexer, Interpreter, Resolver, Component, Buffer,
and several other objects must create each other transparently,
passing the appropriate parameters to the right class, possibly
substituting other subclasses for any of these objects.
The main features of C<Class::Container> are:
=over 4
=item *
Explicit declaration of containment relationships (aggregation,
factory creation, etc.)
=item *
Declaration of constructor parameters accepted by each member in a
class framework
=item *
Transparent passing of constructor parameters to the class
that needs them
=item *
Ability to create one (automatic) or many (manual) contained
objects automatically and transparently
=back
=head2 Scenario
Suppose you've got a class called C<Parent>, which contains an object of
the class C<Child>, which in turn contains an object of the class
C<GrandChild>. Each class creates the object that it contains.
Each class also accepts a set of named parameters in its
C<new()> method. Without using C<Class::Container>, C<Parent> will
have to know all the parameters that C<Child> takes, and C<Child> will
have to know all the parameters that C<GrandChild> takes. And some of
the parameters accepted by C<Parent> will really control aspects of
C<Child> or C<GrandChild>. Likewise, some of the parameters accepted
by C<Child> will really control aspects of C<GrandChild>. So, what
happens when you decide you want to use a C<GrandDaughter> class
instead of the generic C<GrandChild>? C<Parent> and C<Child> must be
modified accordingly, so that any additional parameters taken by
C<GrandDaughter> can be accommodated. This is a pain - the kind of
pain that object-oriented programming was supposed to shield us from.
Now, how can C<Class::Container> help? Using C<Class::Container>,
each class (C<Parent>, C<Child>, and C<GrandChild>) will declare what
arguments they take, and declare their relationships to the other
classes (C<Parent> creates/contains a C<Child>, and C<Child>
creates/contains a C<GrandChild>). Then, when you create a C<Parent>
object, you can pass C<< Parent->new() >> all the parameters for all
three classes, and they will trickle down to the right places.
Furthermore, C<Parent> and C<Child> won't have to know anything about
the parameters of its contained objects. And finally, if you replace
C<GrandChild> with C<GrandDaughter>, no changes to C<Parent> or
C<Child> will likely be necessary.
=head1 METHODS
=head2 new()
Any class that inherits from C<Class::Container> should also inherit
its C<new()> method. You can do this simply by omitting it in your
class, or by calling C<SUPER::new(@_)> as indicated in the SYNOPSIS.
The C<new()> method ensures that the proper parameters and objects are
passed to the proper constructor methods.
At the moment, the only possible constructor method is C<new()>. If
you need to create other constructor methods, they should call
C<new()> internally.
=head2 __PACKAGE__->contained_objects()
This class method is used to register what other objects, if any, a given
class creates. It is called with a hash whose keys are the parameter
names that the contained class's constructor accepts, and whose values
are the default class to create an object of.
For example, consider the C<HTML::Mason::Compiler> class, which uses
the following code:
__PACKAGE__->contained_objects( lexer => 'HTML::Mason::Lexer' );
This defines the relationship between the C<HTML::Mason::Compiler>
( run in 1.474 second using v1.01-cache-2.11-cpan-39bf76dae61 )