The Decorator Pattern - Decorating Objects - Head First Design Patterns

Download as pdf or txt
Download as pdf or txt
You are on page 1of 31

 Head First Design Patterns

PREV NEXT
⏮ ⏭
2. The Observer Pattern: Keeping your O… 4. The Factory Pattern: Baking with OO …
 

Chapter 3. The Decorator Pattern:


Decorating Objects

Just call this chapter “Design Eye for the Inheritance Guy.” We’ll re-examine
the typical overuse of inheritance and you’ll learn how to decorate your classes at
runtime using a form of object composition. Why? Once you know the tech-
niques of decorating, you’ll be able to give your (or someone else’s) objects new
responsibilities without making any code changes to the underlying classes.

Welcome to Starbuzz Co ee ⬆
Starbuzz Coffee has made a name for itself as the fastest growing coffee shop
around. If you’ve seen one on your local corner, look across the street; you’ll
see another one.

Because they’ve grown so quickly, they’re scrambling to update their order-


ing systems to match their beverage offerings.

When they first went into business they designed their classes like this...

In addition to your coffee, you can also ask for several condiments like
steamed milk, soy, and mocha (otherwise known as chocolate), and have it
all topped off with whipped milk. Starbuzz charges a bit for each of these, so
they really need to get them built into their order system.

Here’s their first attempt...


BRAIN POWER
It’s pretty obvious that Starbuzz has created a maintenance
nightmare for themselves. What happens when the price of
milk goes up? What do they do when they add a new
caramel topping?

Thinking beyond the maintenance problem, which of the de­
sign principles that we’ve covered so far are they violating?

Hint: they’re violating two of them in a big way!


Well, let’s give it a try. Let’s start with the Beverage base class and add instance
variables to represent whether or not each beverage has milk, soy, mocha, and
whip...

Now let’s add in the subclasses, one for each beverage on the menu:


SHARPEN YOUR PENCIL

Write the cost() methods for the following classes (pseudo­Java is okay):

public class Beverage { public class Dar


public double cost() {
public DarkRoa
description
}

public double

}
} }
}

SHARPEN YOUR PENCIL

What requirements or other factors might change that will impact this
design?


M A S T E R A N D S T U D E N T. . .

Master: Grasshopper, it has been some time since our last meeting.
Have you been deep in meditation on inheritance?

Student: Yes, Master. While inheritance is powerful, I have learned that it
doesn’t always lead to the most flexible or maintainable designs.

Master: Ah yes, you have made some progress. So, tell me, my student,
how then will you achieve reuse if not through inheritance?

Student: Master, I have learned there are ways of “inheriting” behavior at
runtime through composition and delegation.

Master: Please, go on...

Student: When I inherit behavior by subclassing, that behavior is set sta­
tically at compile time. In addition, all subclasses must inherit the same
behavior. If however, I can extend an object’s behavior through composi­
tion, then I can do this dynamically at runtime.

Master: Very good, Grasshopper, you are beginning to see the power of
composition.

Student: Yes, it is possible for me to add multiple new responsibilities to
objects through this technique, including responsibilities that were not
even thought of by the designer of the superclass. And, I don’t have to
touch their code!

Master: What have you learned about the effect of composition on main­
taining your code?

Student: Well, that is what I was getting at. By dynamically composing
objects, I can add new functionality by writing new code rather than alter­
ing existing code. Because I’m not changing existing code, the chances
of introducing bugs or causing unintended side effects in pre­existing
code are much reduced.

Master: Very good. Enough for today, Grasshopper. I would like for you
to go and meditate further on this topic... Remember, code should be
closed (to change) like the lotus flower in the evening, yet open (to exten­
sion) like the lotus flower in the morning.

The Open-Closed Principle


Grasshopper is on to one of the most important design principles:

DESIGN PRINCIPLE
Classes should be open for extension, but closed for
modification.


Come on in; we’re open. Feel free to extend our classes with any new behavior
you like. If your needs or requirements change (and we know they will), just go
ahead and make your own extensions.

Sorry, we’re closed. That’s right, we spent a lot of time getting this code correct
and bug free, so we can’t let you alter the existing code. It must remain closed to
modification. If you don’t like it, you can speak to the manager.

Our goal is to allow classes to be easily extended to incorporate new behav-


ior without modifying existing code. What do we get if we accomplish this?
Designs that are resilient to change and flexible enough to take on new func-
tionality to meet changing requirements.


THERE ARE NO DUMB QUESTIONS


Q: Q: Open for extension and closed for modi­
fication? That sounds very contradictory.
How can a design be both?

A: A: That’s a very good question. It certainly
sounds contradictory at first. After all, the less
modifiable something is, the harder it is to ex­
tend, right?

As it turns out, though, there are some clever
OO techniques for allowing systems to be ex­
tended, even if we can’t change the underlying
code. Think about the Observer Pattern (in
Chapter 2)... by adding new Observers, we can
extend the Subject at any time, without adding
code to the Subject. You’ll see quite a few
more ways of extending behavior with other
OO design techniques.

Q: Q: Okay, I understand Observable, but how
do I generally design something to be ex­
tensible, yet closed for modification?

A: A: Many of the patterns give us time­tested de­
signs that protect your code from being modi­
fied by supplying a means of extension. In this
chapter you’ll see a good example of using the
Decorator Pattern to follow the Open­Closed
principle.

Q: Q: How can I make every part of my design
follow the Open­Closed Principle?

A: A: Usually, you can’t. Making OO design flexi­
ble and open to extension without the modifica­
tion of existing code takes time and effort. In
general, we don’t have the luxury of tying down
every part of our designs (and it would proba­
bly be wasteful). Following the Open­Closed
Principle usually introduces new levels of ab­
straction, which adds complexity to our code.
You want to concentrate on those areas that
are most likely to change in your designs and
apply the principles there.

Q: Q: How do I know which areas of change
are more important?

A: A: That is partly a matter of experience in de­

signing OO systems and also a matter of
knowing the domain you are working in. Look­
ing at other examples will help you learn to
identify areas of change in your own designs.

While it may seem like a contradiction, there are techniques for allow-
ing code to be extended without direct modification.

Be careful when choosing the areas of code that need to be extended;


applying the Open-Closed Principle EVERYWHERE is wasteful and
unnecessary, and can lead to complex, hard-to-understand code.

Meet the Decorator Pattern


Okay, we’ve seen that representing our beverage plus condiment pricing scheme
with inheritance has not worked out very well—we get class explosions and rigid
designs, or we add functionality to the base class that isn’t appropriate for some
of the subclasses.

So, here’s what we’ll do instead: we’ll start with a beverage and “decorate” it
with the condiments at runtime. For example, if the customer wants a Dark Roast
with Mocha and Whip, then we’ll:

① Take a DarkRoast object

② Decorate it with a Mocha object

③ Decorate it with a Whip object

④ Call the cost() method and rely on delegation to add on the condi-
ment costs

Okay, but how do you “decorate” an object, and how does delegation come into
this? A hint: think of decorator objects as “wrappers.” Let’s see how this works...


Constructing a drink order with Decorators
① We start with our DarkRoast object.

② The customer wants Mocha, so we create a Mocha object and wrap


it around the DarkRoast.


③ The customer also wants Whip, so we create a Whip decorator and
wrap Mocha with it.

NOTE
So, a DarkRoast wrapped in Mocha and Whip is still a
Beverage and we can do anything with it we can do
with a DarkRoast, including call its cost() method.

④ Now it’s time to compute the cost for the customer. We do this by
calling cost() on the outermost decorator, Whip, and Whip is going to
delegate computing the cost to the objects it decorates. Once it gets a
cost, it will add on the cost of the Whip.

OKAY, HERE’S WHAT WE KNOW SO FAR...


Decorators have the same supertype as the objects they decorate.

You can use one or more decorators to wrap an object. ⬆


Given that the decorator has the same supertype as the object it decorates,
we can pass around a decorated object in place of the original (wrapped)
object.

The decorator adds its own behavior either before and/or after delegating to
the object it decorates to do the rest of the job.

NOTE
Key point!

Objects can be decorated at any time, so we can decorate objects dynami-


cally at runtime with as many decorators as we like.

Now let’s see how this all really works by looking at the Decorator Pattern
definition and writing some code.

The Decorator Pattern defined


Let’s first take a look at the Decorator Pattern description:

NOTE
The Decorator Pattern attaches additional responsibilities to
an object dynamically. Decorators provide a flexible alterna­
tive to subclassing for extending functionality.

While that describes the role of the Decorator Pattern, it doesn’t give us a lot of
insight into how we’d apply the pattern to our own implementation. Let’s take a
look at the class diagram, which is a little more revealing (on the next page we’ll
look at the same structure applied to the beverage problem).


Decorating our Beverages
Okay, let’s work our Starbuzz beverages into this framework...

BRAIN POWER
Before going further, think about how you’d implement the
cost() method of the coffees and the condiments. Also think
about how you’d implement the getDescription() method of
the condiments.

Cubicle Conversation
Some confusion over Inheritance versus Composition


Sue: What do you mean?
Mary: Look at the class diagram. The CondimentDecorator is extending the
Beverage class. That’s inheritance, right?

Sue: True. I think the point is that it’s vital that the decorators have the same type
as the objects they are going to decorate. So here we’re using inheritance to
achieve the type matching, but we aren’t using inheritance to get behavior.

Mary: Okay, I can see how decorators need the same “interface” as the compo-
nents they wrap because they need to stand in place of the component. But where
does the behavior come in?

Sue: When we compose a decorator with a component, we are adding new be-
havior. We are acquiring new behavior not by inheriting it from a superclass, but
by composing objects together.

Mary: Okay, so we’re subclassing the abstract class Beverage in order to have
the correct type, not to inherit its behavior. The behavior comes in through the
composition of decorators with the base components as well as other decorators.

Sue: That’s right.

Mary: Ooooh, I see. And because we are using object composition, we get a
whole lot more flexibility about how to mix and match condiments and bever-
ages. Very smooth.

Sue: Yes, if we rely on inheritance, then our behavior can only be determined sta-
tically at compile time. In other words, we get only whatever behavior the super-
class gives us or that we override. With composition, we can mix and match dec-
orators any way we like... at runtime.

Mary: And as I understand it, we can implement new decorators at any time to
add new behavior. If we relied on inheritance, we’d have to go in and change ex-
isting code any time we wanted new behavior.

Sue: Exactly.

Mary: I just have one more question. If all we need to inherit is the type of the
component, how come we didn’t use an interface instead of an abstract class for
the Beverage class?

Sue: Well, remember, when we got this code, Starbuzz already had an abstract
Beverage class. Traditionally the Decorator Pattern does specify an abstract com-
ponent, but in Java, obviously, we could use an interface. But we always try to
avoid altering existing code, so don’t “fix” it if the abstract class will work just
fine.

New barista training


Make a picture for what happens when the order is for a “double mocha soy latte
with whip” beverage. Use the menu to get the correct prices, and draw your pic-
ture using the same format we used earlier (from a few pages back):



SHARPEN YOUR PENCIL

Draw your picture here.

Writing the Starbuzz code


It’s time to whip this design into some real code.

Let’s start with the Beverage class, which doesn’t need to change from Star-
buzz’s original design. Let’s take a look:

Beverage is simple enough. Let’s implement the abstract class for the Condi-
ments (Decorator) as well:


Coding beverages
Now that we’ve got our base classes out of the way, let’s implement some
beverages. We’ll start with Espresso. Remember, we need to set a description
for the specific beverage and also implement the cost() method.

Coding condiments
If you look back at the Decorator Pattern class diagram, you’ll see we’ve
now written our abstract component (Beverage), we have our concrete com-
ponents (HouseBlend), and we have our abstract decorator (CondimentDec-
orator). Now it’s time to implement the concrete decorators. Here’s Mocha:


On the next page we’ll actually instantiate the beverage and wrap it with all its
condiments (decorators), but first...

EXERCISE

Write and compile the code for the other Soy and Whip condiments. You’ll
need them to finish and test the application.

Serving some co ees


Congratulations. It’s time to sit back, order a few coffees, and marvel at the flexi-
ble design you created with the Decorator Pattern.

Here’s some test code*to make orders:

* We’re going to see a much better way of creating decorated objects


when we cover the Factory and Builder Design Patterns. Please note that
the Builder Pattern is covered in the Appendix.

Now, let’s get those orders in:




THERE ARE NO DUMB QUESTIONS


Q: Q: I’m a little worried about code that might
test for a specific concrete component—
say, HouseBlend—and do something, like
issue a discount. Once I’ve wrapped the
HouseBlend with decorators, this isn’t go­
ing to work anymore.

A: A: That is exactly right. If you have code that
relies on the concrete component’s type, deco­
rators will break that code. As long as you only
write code against the abstract component
type, the use of decorators will remain trans­
parent to your code. However, once you start
writing code against concrete components,
you’ll want to rethink your application design
and your use of decorators.

Q: Q: Wouldn’t it be easy for some client of a
beverage to end up with a decorator that
isn’t the outermost decorator? Like if I had
a DarkRoast with Mocha, Soy, and Whip, it
would be easy to write code that somehow
ended up with a reference to Soy instead of
Whip, which means it would not include
Whip in the order.

A: A: You could certainly argue that you have to
manage more objects with the Decorator Pat­
tern and so there is an increased chance that
coding errors will introduce the kinds of prob­
lems you suggest. However, decorators are
typically created by using other patterns like
Factory and Builder. Once we’ve covered
these patterns, you’ll see that the creation of
the concrete component with its decorator is
“well encapsulated” and doesn’t lead to these
kinds of problems.

Q: Q: Can decorators know about the other
decorations in the chain? Say I wanted my
getDescription() method to print “Whip,
Double Mocha” instead of “Mocha, Whip,
Mocha.” That would require that my outer­
most decorator know all the decorators it is
wrapping.

A: A: Decorators are meant to add behavior to the
object they wrap. When you need to peek at
multiple layers into the decorator chain, you
are starting to push the decorator beyond its


true intent. Nevertheless, such things are pos­
sible. Imagine a CondimentPrettyPrint decora­
tor that parses the final decription and can print
“Mocha, Whip, Mocha” as “Whip, Double
Mocha.” Note that getDescription() could return
an ArrayList of descriptions to make this
easier.

SHARPEN YOUR PENCIL

Our friends at Starbuzz have introduced sizes to their menu. You can
now order a coffee in tall, grande, and venti sizes (translation: small,
medium, and large). Starbuzz saw this as an intrinsic part of the coffee
class, so they’ve added two methods to the Beverage class: setSize()
and getSize(). They’d also like for the condiments to be charged accord­
ing to size, so for instance, Soy costs 10¢, 15¢, and 20¢, respectively, for
tall, grande, and venti coffees. The updated Beverage class is shown
below.

How would you alter the decorator classes to handle this change in
requirements?

public abstract class Beverage {


public enum Size { TALL, GRANDE, VENTI };
Size size = Size.TALL;
String description = "Unknown Beverage";
public String getDescription() {
return description;
}
public void setSize(Size size) {
this.size = size;
}
public Size getSize() {
return this.size;
}
public abstract double cost();
}

Real World Decorators: Java I/O


The large number of classes in the java.io package is... overwhelming. Don’t feel
alone if you said “whoa” the first (and second and third) time you looked at this
API. But now that you know the Decorator Pattern, the I/O classes should make
more sense since the java.io package is largely based on Decorator. Here’s a typi-
cal set of objects that use decorators to add functionality to reading data from a
file:


BufferedInputStream and LineNumberInputStream both extend FilterInput-
Stream, which acts as the abstract decorator class.

Decorating the java.io classes

You can see that this isn’t so different from the Starbuzz design. You should now
be in a good position to look over the java.io API docs and compose decorators
on the various input streams.

You’ll see that the output streams have the same design. And you’ve probably al-
ready found that the Reader/Writer streams (for character-based data) closely
mirror the design of the streams classes (with a few differences and inconsisten-
cies, but close enough to figure out what’s going on).

Java I/O also points out one of the downsides of the Decorator Pattern: designs
using this pattern often result in a large number of small classes that can be over-
whelming to a developer trying to use the Decorator-based API. But now that
you know how Decorator works, you can keep things in perspective and when
you’re using someone else’s Decorator-heavy API, you can work through how
their classes are organized so that you can easily use wrapping to get the behavior
you’re after.

Writing your own Java I/O Decorator


Okay, you know the Decorator Pattern, you’ve seen the I/O class diagram. You
should be ready to write your own input decorator.

How about this: write a decorator that converts all uppercase characters to lower-
case in the input stream. In other words, if we read in “I know the Decorator Pat-
tern therefore I RULE!” then your decorator converts this to “i know the decora-
tor pattern therefore i rule!”

REMEMBER: we don’t provide import and package statements in the code
listings. Get the complete source code from
http://wickedlysmart.com/head-first-design-patterns/
(http://wickedlysmart.com/head-first-design-patterns/).

Test out your new Java I/O Decorator


Write some quick code to test the I/O decorator:


GIVE IT A SPIN


PAT T E R N S E X P O S E D

This week’s interview: Confessions of a Decorator

Head First: Welcome, Decorator Pattern. We’ve heard that you’ve been
a bit down on yourself lately?

Decorator: Yes, I know the world sees me as the glamorous design pat­
tern, but you know, I’ve got my share of problems just like everyone.

HeadFirst: Can you perhaps share some of your troubles with us?

Decorator: Sure. Well, you know I’ve got the power to add flexibility to
designs, that much is for sure, but I also have a dark side. You see, I can
sometimes add a lot of small classes to a design and this occasionally re­
sults in a design that’s less than straightforward for others to understand.

HeadFirst: Can you give us an example?

Decorator: Take the Java I/O libraries. These are notoriously difficult for
people to understand at first. But if they just saw the classes as a set of
wrappers around an InputStream, life would be much easier.

HeadFirst: That doesn’t sound so bad. You’re still a great pattern, and
improving this is just a matter of public education, right?

Decorator: There’s more, I’m afraid. I’ve got typing problems: you see,
people sometimes take a piece of client code that relies on specific types
and introduce decorators without thinking through everything. Now, one
great thing about me is that you can usually insert decorators trans­
parently and the client never has to know it’s dealing with a decora­
tor. But like I said, some code is dependent on specific types and when
you start introducing decorators, boom! Bad things happen.

HeadFirst: Well, I think everyone understands that you have to be care­
ful when inserting decorators. I don’t think this is a reason to be too down
on yourself.

Decorator: I know, I try not to be. I also have the problem that introduc­
ing decorators can increase the complexity of the code needed to instan­
tiate the component. Once you’ve got decorators, you’ve got to not only
instantiate the component, but also wrap it with who knows how many
decorators.

HeadFirst: I’ll be interviewing the Factory and Builder patterns next week
—I hear they can be very helpful with this?

Decorator: That’s true; I should talk to those guys more often.

HeadFirst: Well, we all think you’re a great pattern for creating flexible
designs and staying true to the Open­Closed Principle, so keep your chin
up and think positively!

Decorator: I’ll do my best, thank you.

Tools for your Design Toolbox


You’ve got another chapter under your belt and a new principle and pattern in the
toolbox.



BULLET POINTS

Inheritance is one form of extension, but not necessarily the best
way to achieve flexibility in our designs.

In our designs we should allow behavior to be extended without the
need to modify existing code.

Composition and delegation can often be used to add new behav­
iors at runtime.

The Decorator Pattern provides an alternative to subclassing for ex­
tending behavior.

The Decorator Pattern involves a set of decorator classes that are
used to wrap concrete components.

Decorator classes mirror the type of the components they decorate.
(In fact, they are the same type as the components they decorate,
either through inheritance or interface implementation.)

Decorators change the behavior of their components by adding new
functionality before and/or after (or even in place of) method calls to
the component.

You can wrap a component with any number of decorators.

Decorators are typically transparent to the client of the component;
that is, unless the client is relying on the component’s concrete type.

Decorators can result in many small objects in our design, and
overuse can be complex.


SHARPEN YOUR PENCIL SOLUTION

Write the cost() methods for the following classes (pseudo­Java is okay).
Here’s our solution:

public class Beverage {

// declare instance variables for milkCost,


// soyCost, mochaCost, and whipCost, and
// getters and setters for milk, soy, mocha
// and whip.

public double cost() {

float condimentCost = 0.0;


if (hasMilk()) {
condimentCost += milkCost;
}
if (hasSoy()) {
condimentCost += soyCost;
}
if (hasMocha()) {
condimentCost += mochaCost;
}
if (hasWhip()) {
condimentCost += whipCost;
}
return condimentCost;
}
}

public class DarkRoast extends Beverage {

public DarkRoast() {
description = "Most Excellent Dark Roast";
}

public double cost() {


return 1.99 + super.cost();
}
}

SHARPEN YOUR PENCIL SOLUTION

New barista training

“double mocha soy latte with whip”


SHARPEN YOUR PENCIL SOLUTION

Our friends at Starbuzz have introduced sizes to their menu. You can
now order a coffee in tall, grande, and venti sizes (for us normal folk:
small, medium, and large). Starbuzz saw this as an intrinsic part of the
coffee class, so they’ve added two methods to the Beverage class: set­
Size() and getSize(). They’d also like for the condiments to be charged
according to size, so for instance, Soy costs 10¢, 15¢, and 20¢, respec­
tively, for tall, grande, and venti coffees.

How would you alter the decorator classes to handle this change in re­
quirements? Here’s our solution.

Recommended / Playlists / History / Topics / Tutorials / Settings / Get the App / Sign Out
© 2018 Safari. Terms of Service / Privacy Policy
PREV NEXT
⏮ ⏭
2. The Observer Pattern: Keeping your O… 4. The Factory Pattern: Baking with OO …

You might also like