Class::ClassDecorator - Dynamically decorate classes instead of objects using NEXT
use Class::ClassDecorator; my $class = Class::ClassDecorator::decorate( 'Foo::Base' => 'Foo::Bar' => 'Foo::Baz' ); my $object = $class->new; # may be implemented in any of the three classes specified. $object->foo(); # same thing, different internal implementation my $class = Class::ClassDecorator::hierarchy( 'Foo::Base' => 'Foo::Bar' => 'Foo::Baz' );
This module helps you use classes as decorators for other classes.
It provides some syntactic sugar for dynamically constructing a unique subclass which exists solely to represent a set of decorations to a base class.
This is useful when you have a base module and you want to add different behaviors (decorations) to it at a class level, as opposed to decorating a single object.
Given a base class of
Foo, and possible decorating classes
Foo::Oversize, we could construct new classes that used any possible combination of the decorating classes.
With regular inheritance, we'd have to create many classes like
Foo::OversizePersistent and so on. Plus we'd still need to call methods as
NEXT::foo() from within the decorating classes or risk breaking another decorating class's behavior, because it expects to override certain methods.
NEXT.pm, we can easily implement our desired behavior by creating a single subclass that inherits from all of the decorating classes and the base class.
So to implement a
Foo subclass that incorporate persistence and caching, we could create a hierarchy like:
Foo::Persistent Foo::WithCache Foo \ | / \ | / our subclass here
This module automates the creation of that subclass.
However, sometimes the above diagram creates a problem. The
NEXT.pm module does a depth-first, left to right search for methods. If, in our
Foo example above, the
Foo::Persistent classes both inherited from some other class, like
Bar, we'd end up with this diagram:
Bar / \ / \ / \ Foo::Persistent Foo::WithCache Foo \ | / \ | / our subclass here
This means that we call a method implemented in
new() on our subclass, it will get called multiple times, and the first time will immediately be after
This may cause problems, so it may be preferable to create an actual hierarchy, or the appearance of one, that looks something like this:
Bar | Foo | Foo::WithCache | Foo::Persistent | our subclass here
In this case, we would have
$self->SUPER::new() (using Perl's real
SUPER:: dispatch) from the Foo class's
new() method. This guarantees that
Bar->new() is called once, and that it happens after
Foo->new() is called.
To do that, call the
hierarchy() method instead of
decorate(). Classes that expect to decorate other classes in this manner should use
super::foo() to call their "parent's"
foo() method. In actuality,
Foo::WithCache does not inherit from
Foo, because this module does not touch
Simply call the
hierarchy() function with a list of classes, starting with the base class you want to decorate, followed by each decorator. The function returns a string containing the new class name.
The created classes are cached, so multiple calls with the same arguments always return the same subclass name.
The order of the arguments is significant. Methods are searched for in last to first order, so that the base class is called last. With our "persistent caching Foo" example from the DESCRIPTION, we can pretend that we have created a hierarchy like this:
Foo | Foo::WithCache | Foo::Persistent | our subclass here
Decorating classes must always use
super:: to call methods for classes "above" them in the (fictional) hierarchy, rather than
Decorating classes must not actually inherit from the base class. They are, of course, free to inherit from other classes if they wish, but the author should take care in the use of
Nag the author via email at email@example.com.
Dave Rolsky <firstname.lastname@example.org>
Thanks to Ken Fox for suggesting this implementation.
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
The full text of the license can be found in the LICENSE file included with this module.