Badger::Factory
- NAME
- SYNOPSIS
- DESCRIPTION
- Defining a Factory Module
- Using Your Factory Module
- METHODS
- new()
- path($path)
- names($names)
- default($name)
- items(%items)
- item($name,@args)
- INTERNAL METHODS
- type_args(@args)
- find($type,\@args)
- load(@module_names)
- found($name,$item,\@args)
- found_module($module)
- found_array(\@array)
- found_hash(\%hash)
- found_scalar(\$scalar)
- found_object($object)
- construct($name,$class,\@args)
- result($name,$result,\@args)
- module_names($type)
- not_found($name,@args)
- can($method)
- AUTOLOAD(@args)
- AUTHOR
- COPYRIGHT
- SEE ALSO
This module is designed to be subclassed to create factory classes that automatically load modules and instantiate objects on demand.
package My::Widgets; use base 'Badger::Factory'; # tell the base class factory what we create our $ITEM = 'widget'; our $ITEMS = 'widgets'; # define module search path for widgets our $WIDGET_PATH = ['My::Widget', 'Your::Widget']; # lookup table for any non-standard spellings/capitalisations/paths our $WIDGETS = { url => 'My::Widget::URL', # non-standard capitalisation color => 'My::Widget::Colour', # different spelling amp => 'Nigels::Amplifier', # different path };
You can then use it like this:
use My::Widgets; # class methods (note: widget() is singular) $w = My::Widgets->widget( foo => { msg => 'Hello World' } ); # same as: use My::Widget::Foo; $w = My::Widget::Foo({ msg => 'Hello World' }); # add/update widgets lookup table (note: widgets() is plural) My::Widgets->widgets( extra => 'Another::Widget::Module', super => 'Golly::Gosh', ); # now load and instantiate new widget modules $w = My::Widgets->widget( extra => { msg => 'Hello Badger' } );
You can also create factory objects:
my $factory = My::Widgets->new( widget_path => ['His::Widget', 'Her::Widget'], widgets => { extra => 'Another::Widget::Module', super => 'Golly::Gosh', } ); $w = $factory->widget( foo => { msg => 'Hello World' } );
The Badger::Factory::Class module can be used to simplify the process of defining factory subclasses.
package My::Widgets; use Badger::Factory::Class item => 'widget', path => 'My::Widget Your::Widget'; widgets => { extra => 'Another::Widget::Module', super => 'Golly::Gosh', };
This module implements a base class factory object for loading modules
and instantiating objects on demand. It originated in the Template::Plugins module, evolved
over time in various directions for other projects, and was eventually
pulled back into line to become Badger::Factory
.
The Badger::Factory
module isn't designed to be used by
itself. Rather it should be used as a base class for your own factory
modules. For example, suppose you have a project which has lots of
My::Widget::*
modules. You can define a factory for them
like so:
package My::Widgets; use base 'Badger::Factory'; our $ITEM = 'widget'; our $ITEMS = 'widgets'; our $WIDGET_PATH = ['My::Widget', 'Your::Widget']; our $WIDGET_DEFAULT = 'foo'; our $WIDGET_NAMES = { html => 'HTML', }; # lookup table for any non-standard spellings/capitalisations/paths our $WIDGETS = { url => 'My::Widget::URL', # non-standard capitalisation color => 'My::Widget::Colour', # different spelling amp => 'Nigels::Amplifier', # different path }; 1;
The $ITEM
and $ITEMS
package variables are used
to define the singular and plural names of the items that the factory is
responsible for. In this particular case, the $ITEMS
declaration isn't strictly necessary because the module would correctly
"guess" the plural name widgets
from the singular
widget
defined in $ITEM
. However, this is only
provided as a convenience for those English words that pluralise
regularly and shouldn't be relied upon to work all the time. See the pluralise()
method in Badger::Utils
for further information, and explicitly specify the plural in
$ITEMS
if you're in any doubt.
The $WIDGET_PATH
is used to define one or more base module
names under which your widgets are located. The name of this variable is
derived from the upper case item name in $ITEM
with
_PATH
appended. In this example, the factory will look for
the Foo::Bar
module as either
My::Widget::Foo::Bar
or Your::Widget::Foo::Bar
.
The $WIDGET_DEFAULT
specifies the default item name to use
if a request is made for a module using an undefined or false name. If
you don't specify any value for a default then it uses the literal string
default
. Adding a default
entry to your
$WIDGET_NAMES
or $WIDGETS
will have the same
effect.
The $WIDGET_NAMES
is used to define any additional name
mappings. This is usually required to handle alternate spellings or
unusual capitalisations that the default name mapping algorithm would get
wrong. For example, a request for an html
widget would look
for My::Widget::Html
or Your::Widget::Html
.
Adding a $WIDGET_MAP
entry mapping html
to
HTML
will instead send it looking for
My::Widget::HTML
or Your::Widget::HTML
.
If you've got any widgets that aren't located in one of these locations,
or if you want to provide some aliases to particular widgets then you can
define them in the $WIDGETS
package variable. The name of
this variable is the upper case conversion of the value defined in the
$ITEMS
package variable.
Now that you've define a factory module you can use it like this.
use My::Widgets; my $widgets = My::Widgets->new; my $foo_bar = $widgets->widget('Foo::Bar');
The widget()
method is provided to load a widget module and
instantiate a widget object.
The above example is equivalent to:
use My::Widget::Foo::Bar; my $foo_bar = My::Widget::Foo::Bar->new;
Although it's not strictly equivalent because the factory could
just has easily have loaded it from Your::Widget::Foo::Bar
in the case that My::Widget::Foo::Bar
doesn't exist.
You can specify additional arguments that will be forwarded to the object constructor method.
my $foo_bar = $widgets->widget('Foo::Bar', x => 10, y => 20);
If you've specified a $WIDGET_DEFAULT
for your factory then
you can call the widget() method without any
arguments to get the default object.
my $widget = $widgets->widget;
You can use the default() method to change the default module.
$widgets->default('bar');
The factory module can be customised using configuration parameters. For
example, you can provide additional values for the
widget_path
, or define additional widgets:
my $widgets = My::Widgets->new( widget_path => ['His::Widget', 'Her::Widget'], widgets => { extra => 'Another::Widget::Module', super => 'Golly::Gosh', } );
The factory module is an example of a prototype() module. This means that
you can call the widget()
method as a class method to save
yourself of explicitly creating a factory object.
my $widget = My::Widgets->widget('Foo::Bar');
Constructor method to create a new factory module.
my $widgets = My::Widgets->new;
Used to get or set the factory module path.
my $path = $widgets->path; $widgets->path(['My::Widgets', 'Your::Widgets', 'Our::Widgets']);
Calling the method with arguments replaces any existing list.
Used to get or set the names mapping table.
my $names = $widgets->names; $widgets->names({ html => 'HTML' });
Calling the method with arguments replaces any existing names table.
Used to fetch or update the lookup table for mapping names to modules.
my $items = $widgets->items; $widgets->items( foo => 'My::Plugin::Foo' );
Calling the method with arguments (named parameters or a hash reference) will add the new definitions into the existing table.
This method can also be aliased by the plural name defined in
$ITEMS
in your subclass module.
$widgets->widgets;
Method to load a module and instantiate an object.
my $widget = $widgets->item('Foo');
Any additional arguments provided after the module name are forwarded to
the object's new()
constructor method.
my $widget = $widgets->item( Foo => 10, 20 );
This method can also be aliased by the singular name defined in
$ITEM
in your subclass module.
my $widget = $widgets->widget( Foo => 10, 20 );
The module name specified can be specified in lower case. The name is capitalised as a matter of course.
# same as Foo my $widget = $widgets->widget( foo => 10, 20 );
Multi-level names can be separated with dots rather than ::
.
This is in keeping with the convention used in the Template Toolkit. Each
element after a dot is capitalised.
# same as Foo::Bar my $widget = $widgets->widget( 'foo.bar' => 10, 20 );
This method can be re-defined by a subclass to perform any pre-manipulation on the arguments passed to the item() method. The first argument is usually the type (i.e. name) of module requested, followed by any additional arguments for the object constructor.
my ($self, $type, @args) = @_;
The method should return them like so:
return ($type, @args);
This method is called to find and dynamically load a module if it doesn't
already have an entry in the internal items
table. It
iterates through each of the base paths for the factory and calls the load() method to see if the module can be found
under that prefix.
This method is called to dynamically load a module. It iterates through
each of the module name passed as arguments until it successfully loads
one. At that point it returns the module name that was successfully
loaded and ignores the remaining arguments. If none of the modules can be
loaded then it returns undef
This method is called when an item has been found, either in the internal
items
lookup table, or by a call to find(). The $item
argument is usually a module name that is forwarded onto found_module(). However, it can also be a
reference which will be forwarded onto one of the following methods
depending on its type: found_array(),
found_hash(), found_scalar(), found_object() (and in theory,
found_regex()
, found_glob()
and maybe others,
but they're not implemented).
The result returned by the appropriate found_XXXXX()
method
will then be forwarded onto the result()
method. The method returns the result from the result() method.
This method is called when a requested item has been mapped to a module name. The module is loaded if necessary, then the construct() method is called to construct an object.
An entry in the items
(aka widgets
in our
earlier example) table can be a reference to a list containing a module
name and a separate class name.
my $widgets = My::Widgets->new( widgets => { wizbang => ['Wiz::Bang', 'Wiz::Bang::Bash'], }, );
If the wizbang
widget is requested from the
My::Widgets
factory in the example above, then the found() method will call
found_array()
, passing the array reference as an argument.
The module listed in the first element is loaded. The class name in the second element is then used to instantiate an object.
This method isn't implemented in the base class, but can be defined by subclasses to handle the case where a request is mapped to a hash reference.
This method isn't implemented in the base class, but can be defined by subclasses to handle the case where a request is mapped to a scalar reference.
This method isn't defined in the base class, but can be defined by subclasses to handle the case where a request is mapped to an existing object.
This method instantiates a $class
object using the arguments
provided. In the base class this method simply calls:
$class->new(@$args);
This method is called at the end of a successful request after an object
has been instantiated (or perhaps re-used from an internal cache). In the
base class it simply returns $result
but can be redefined in
a subclass to do something more interesting.
This method performs the necessary mapping from a requested module name to its canonical form.
This method is called when the requested item is not found. The method
simply throws an error using the not_found
message format.
The method can be redefined in subclasses to perform additional fallback
handing.
This method implements the magic to ensure that the item-specific
accessor methods (e.g. widget()
/widgets()
) are
generated on demand.
Andy Wardley http://wardley.org/
Copyright (C) 2006-2009 Andy Wardley. All Rights Reserved.
This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself.