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:
# 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.
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.
# 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.
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.
Search Encyclopedia
|
Featured Article
|