Decorator pattern
In computer programming, the decorator pattern is one of design patterns that is to attach additional logic to an existing object.
When: Something about an object needs to change. Objects can have attributes
that change something about them.
Decorators provide a flexible alternative to subclassing for extending
functionality.
The joy of patterns used stacking burger toppings as an example. It's a good
example. Lets use taco toppings instead, so we aren't copying them too
blatantly. Lets imagine that there is a taco concession in a mall. We won't call
it a Mexican restaurant. That would be a stretch. Most of their tacos sit under
a heat lamp, pre-made, waiting for someone to order the standard taco. A rash
of bowel disrupting bacteria outbreaks brought suspicion on the heat lamps, so
people began ordering tacos with and without all kinds of weird toppings in
attempt to foil the pre-making efforts and get a fresh taco. The concessions
stand management found that the cashiers were making a lot of errors adding up the costs
of the toppings, so they complained to the corporate office. Corporate office
searches the web for "a programmer that doesn't interview like they are reading
from a script and who doesn't design patterns using taco toppings like the last
guy", and hires the first person that comes up: a Perl programmer!
[Actually, thats not true. On Google, top rankings for this search are held by
Phillip Kerman, a Flash ActionScript programmer, and Jeff Turner, a Java programmer].
This programmer could write something like:
There are two gotchas here, though. What if we want a taco with extra, extra
tomatos? Topping::Tomato would be told to inherit itself. This would create an
endless loop! All tomatos would have tomatos are their parent, not just the
last one added. Base taco would be forgotten about. The real problem here is
that we're modifying the whole class - not just the particular instance of the
tomato we added last. This would keep us from using a multithreaded cash
register shared by two people, and it would keep us from having two taco orders
on the same tab, each with different toppings. Dynamic inheritance is a cool
trick, but you must remember that its effects are global. Reserve it for
creating objects of a new, unique name, of user specification, and perhaps a
few similar applications. See BeanPattern and AbstractFactory for more on custom-crafted
objects. For some reason, this mess reminds me of SelfJoiningData.
For our purposes, though, this won't fly. The linked list approach is the right
approach. We need to instantiate individual toppings as objects, so that they
each have private data. In this private data, we need to store the
relationship: what the topping is topping is an attribute of each topping.
See InstanceVariables for more on keeping data private to an instance
of an object.
For yet another approach, see the aggregate pattern.
For the sake of simplicity and clarity, each of these approaches has a
different API. There is no reason they couldn't have been done consistently.
# in a file named Taco.pm:
package Taco;
use ImplicitThis; ImplicitThis::imply();
sub new { bless { price=>5.95}, $_[0]; }
sub query_price { return $price; }
# in a file named TacoWithLettuce.pm:
package TacoWithLettuce;
use ImplicitThis; ImplicitThis::imply();
@ISA = qw(Taco);
sub query_price { return $this->Taco::query_price() + 0.05; }
# in a file named TacoWithTomato.pm:
package TacoWithTomato;
use ImplicitThis; ImplicitThis::imply();
@ISA = qw(Taco);
sub query_price { return $this->Taco::query_price() + 0.10; }
# in a file named TacoWithTomatoAndLettuce.pm:
package TacoWithTomatoAndLettuce;
use ImplicitThis; ImplicitThis::imply();
@ISA = qw(Taco);
sub query_price { return $this->Taco::query_price() + 0.10; }
To do it this way, they would have to create a class for each and every
topping, as well as each and every combination of toppings! With two toppings
this isn't out of hand. With 8 toppings, you've got 256 possible combinations.
With 12 toppings, you've 4096 combinations. Creating a permanent inheritance is
the root of the problem, here. If we could do something similar, but on the
fly, we wouldn't need to write out all of the possible combinations in advance.
We could also make the inheritance chain deeper and deeper as we needed to.
# in a file named Taco.pm:
package Taco;
use ImplicitThis; ImplicitThis::imply();
sub new {
bless { price=>5.95, first_topping=>new Topping::BaseTaco }, $_[0];
}
sub query_price { return $first_topping->query_price(); }
sub add_topping {
my $topping = shift; $topping->isa('Topping') or die "add_topping requires a Topping";
$topping->inherit($first_topping);
$first_topping = $topping;
}
# in a file named Topping.pm:
package Topping.pm;
# this is just a marker class
# in a file named Topping/BaseTaco.pm:
package Topping::BaseTaco;
@ISA = qw(Topping);
sub query_price { return 5.95; }
# in a file named Topping/Lettuce.pm:
package Topping::Lettuce;
@ISA = qw(Topping);
use ImplicitThis; ImplicitThis::imply();
sub query_price { return 0.05 + $this->SUPER::query_price(); }
sub inherit { my $parent = shift; unshift @ISA, $parent; return 1; }
# and so on for each topping...
The astute reader will notice that this isn't much more than a linked list.
Since inheritance is now dynamic, we've gotten rid of needing to explicit
create each combination of toppings. We use inheritance and a recursive
query_price() method that calls its parent's version of the method. When we add
a topping, we tell it to inherit it from the last topping (possibly the base
taco). When someone calls query_price() on the taco, we pass off the request to
our first topping. That topping passes it on down the line, adding them up as
it goes.
# in a file named Taco.pm:
package Taco;
use ImplicitThis; ImplicitThis::imply();
sub new { bless { price=>5.95, top_topping=>new Topping::BaseTaco }, $_[0]; }
sub query_price { return $price; }
sub add_topping {
my $new_topping = shift;
# put the new topping on top of existing toppings. this new topping is now our top topping.
$new_topping->top($top_topping);
$top_topping = $new_topping;
return 1;
}
# in a file named Topping.pm:
package Topping.pm;
use ImplicitThis; ImplicitThis::imply();
sub new {
my $type = shift;
bless { we_top=>undef }, $type;
}
sub top {
my $new_topping = shift; $new_topping->isa('Topping') or die "top must be passed a Topping";
$we_top = $new_topping;
return 1;
}
# in a file named Topping/BaseTaco.pm:
package Topping::BaseTaco;
@ISA = qw(Topping);
sub query_price { return 5.95; }
# in a file named Topping/Lettuce.pm:
package Topping::Lettuce;
use ImplicitThis; ImplicitThis::imply();
@ISA = qw(Topping);
sub query_price { return 0.05 + ($we_top ? $we_top->query_price() : 0); }
There! We finally have something that passes as workable! This solution is good
for something where we want to change arbitrary features of the object without
the containing object (in this case, taco) knowing before hand. We don't make
use of this strength in this example. The query_price() method of the taco
object just passes the request right along, we any math we want can be done.
A two-for-taco-tappings-Tuesday, where
all toppings were half price on Tuesdays, would show off the strengths of the DecoratorPattern.
With a press of a button, a new
object could be pushed onto the front of the list that defined a price method that
just returns half of whatever the price_method() in the next object returns.
The important thing to note is that we can stack logic by inserting one object
in front of another when "has-a" relationships.
External Links
See Also