您的位置:首页 > 其它

Learn Decorator Design Pattern in Easy Steps

2013-07-03 17:50 706 查看
http://www.codeproject.com/Articles/99469/Learn-Decorator-Design-Pattern-in-Easy-Steps

Introduction

Kartoon Kompany approaches you for re-designing their application on the famous Kelvin and Hoppes comic strip!

A quick recap about Kelvin: he is a moody brat with an extremely wild and vivid imagination. His behavior and reactions are completely different and unpredictable, and is based on his current fantasy he has dreamed up. He thinks up imaginary worlds and situations,
like a spaceman battling Zorgs, TracerBazooka – Film Noir detective, imagines he is a T-rex, so on and so forth.

This is their current design:


Collapse |
Copy Code
class Kelvin
{
public:
virtual void MisBehave()
{
std::cout<< "Hi, I am Kelvin the Great!\n";
//yeah, this is just simply writing text to console.
//But imagine if this was some functionality.
//Lets assume writing text to keep things simple
}
};

class SpacemanDecorator: public Kelvin
{
public:
virtual void MisBehave()
{
//when kelvin is spaceman he does something different
std::cout<< "The fearless Spaceman Kpiff battles with the Zork!!!\n";
}
};

class DinoDecorator: public Kelvin
{
public:
virtual void MisBehave()
{
//when kelvin is a Dinosaur, imagine the chaos!
std::cout<< "Kelvin the terrible T-Rex runs rampant in Jurassic park!\n";
}
};

class TracerBazookaDecorator: public Kelvin
{
public:
virtual void MisBehave()
{
//Film-Noir fantasy behavior
std::cout<< "TracerBazooka: Here's looking at you kid.\n";
}
};

Now, Kartoon Kompany has a problem: for the next comic strip, they want to combine a T-Rex episode and a Spaceman episode in one strip. Fans go wild with anticipation.

How do we address this? Simple, let's create a
DinoSpaceman
behavior class where Kelvin acts accordingly, right?

Do you see how easily this could explode into a plethora of subclasses? What about a T-Rex-Spaceman episode? What about a Tracer-TRex episode? Think sub-class nuclear explosion. And we are not even talking of adding new classes like
WithSusie
,
SnowBallFights
,
SantaLetters
, etc.

This is the problem Kartoon Kompany approaches you with. How do you solve this design issue?

Inheritance, though powerful, is not the panacea for Kartoon Kompany’s woes. The problem with the current design is, because all behaviors are defined in sub-classes, we need to keep adding lots of sub-classes, and that is especially difficult considering
Kelvin’s unimaginable imagination.

Enter Decorator Design Pattern (on an F-16 figher jet, with guns blazing)

Okay, so inheritance is out. The problem is that we are trying to define all of Kelvin’s behavior at compile time. Wouldn’t it be great if we could specify Kelvin’s behavior at run-time? Then we don’t have to sub-class, but could use composition at run-time
to define Kelvin’s reactions (author’s note: don’t worry if this doesn’t make sense now, read on).

The Decorator Design Pattern does that for us! Short and sweet, Decorator does following:

Adds behavior to a class dynamically, at run-time. This is in contrast to inheritance, which binds behavior statically, i.e., at compile time.
Is an alternative to sub-classing.

Enough theory, let’s take a plunge and see how the Decorator Design Pattern helps Kelvin.

Apply the simplest of Design Principles: encapsulate and separate what varies. Since the combination of various Kelvin imagination\behavior varies, let's take them out of the Kelvin class hierarchy. I.e.,
TRex
,
Spaceman
,
TracerBazooka
should no longer be derived classes of the
Kelvin
superclass. Since the function
MisBehave()
is common, let's create a common base class, which will be our brand new decorator base class!


Collapse |
Copy Code
//derive from Kelvin so that interface of decorator is same.
//This makes life easy on the client (caller) side
//this will be our base decorator class. We are adding
//new behavior to Kelvin thru composition
class KelvinDecorator: public Kelvin
{
public:

//ctor taking in component to decorate (i.e. Kelvin) as argument.
//We need a Kelvin in order to decorate\add new behavior,
//there is no KelvinDecorator without Kelvin!
KelvinDecorator(Kelvin *kelvinArgument): KelvinComponent(kelvinArgument)
{ }

//overload common interface from Kelvin
virtual void MisBehave()
{
//just ask kelvin to misbehave! The wonder of virtual keyword
//and polymorpishm will call correct derived class,
//either Decorator or main component
kelvinComponent->MisBehave();
}

private:

//Kelvin component, which we need to decorate
Kelvin *kelvinComponent;
};

Now, with this base decorator as the super\base class, we create the Kelvin Decorator specific classes:


Collapse |
Copy Code
// Just derive from our Decorator base-class and overload Kelvin’s interface
class SpacemanDecorator: public KelvinDecorator
{
public:

SpacemanDecorator (Kelvin *kelvin): KelvinDecorator(kelvin)
{ }

virtual void MisBehave()
{
// call previous decorator
KelvinDecorator::MisBehave();

//now add this decorator's additional functionality
std::cout<< "The fearless Spaceman Kpiff battles with the Zork!!!\n";
}
};

class DinoDecorator: public KelvinDecorator
{
public:
DinoDecorator (Kelvin *kelvin): KelvinDecorator(kelvin)
{ }

virtual void MisBehave()
{
// call previous decorator
KelvinDecorator::MisBehave();

//now add this decorator's additional functionality
std::cout<< "Kelvin the terrible T-Rex runs rampant in Jurassic park!\n";
}
};

class TracerBazookaDecorator: public KelvinDecorator
{
public:
TracerBazookaDecorator (Kelvin *kelvin): KelvinDecorator(kelvin)
{ }

virtual void MisBehave()
{
// call previous decorator
KelvinDecorator::MisBehave();

//now add this decorator's additional functionality
std::cout<< "TracerBazooka: Here's looking at you kid.\n";
}
};

That’s it! You’ve implemented the Decorator Design Pattern! Now, we let the beauty of basic OOPS do the work!


Collapse |
Copy Code
Kelvin *mainComponent = new Kelvin();

KelvinDecorator *pAddedFunctionality = new SpacemanDecorator(
new DinoDecorator (
new TracerBazookaDecorator(mainComponent) ) );

pAddedFunctionality->MisBehave();

We’ve done nothing but add one layer of Kelvin on top of another.
KelvinDecorator
does its job and hands the baton to another
KelvinDecorator
, which in turn hands it to another, and so on and so forth. And, we did all this without modifying the main component class
Kelvin
!



Congratulations, you’ve just learnt another design principle: Open-Close principle. Which simply means a class should be closed for modification, but open for extension or change.

For our example, the class
Kelvin
is closed for modification – we never had to modify the
Kelvin
class. But we still were able to alter
Kelvin
’s behavior through decorators (thus we made it open for extension).

And we did this by adding new layers of
KelvinDecorator
at run-time!

And last but not least, the output of our program:



Things work great. Now it is easy to add and combine any of
Kelvin
’s behaviors with very little design change, and more importantly, all this with no change in the
Kelvin
class!

Change in Requirement

Let's see how easy Decorator makes it for handling change\adding new behavior.

Kartoon Kompany wants a new behavior for Kelvin for next week’s comic strip: an episode with Susie.

Our design now makes it very easy to add new behavior. If you need a new
WithSusieDecorator
, just add the new subclass decorator (write a
WithSusieDecorator
and derive it from
KelvinDecorator
) to get the desired functionality. In fact, do try doing this as an assignment to see how easy it is to add a new decorator with very little change to the existing design and code. Try outputting the text “GROSS! Let's hit Susie
D with 1,000 snowballs!!”

You also need to create a new
WithSusieDecorator()
when creating layers of Kelvin Decorators in our client function (you could use a Factory for this, but that is a different design pattern for a different day!).

That’s it! How easily we have added new behavior at runtime. Can you imagine the pain and amount of changes to make if we still had used the static-subclassing way?

Kartoon Kompany is screaming with pleasure at how easy and resilient our new design is to change!

They’re so happy that they plan to introduce you in next week’s strip and let Kelvin hit you with 1,000 snowballs!

A change like that is now easy and systematic, thanks to the Decorator Design Pattern, but whether you want to put up with Kelvin’s antics is up to you!

Decorator Design Pattern Considerations

Okay, now that you’ve understood the Decorator Design Pattern, there are a few more things you should be aware of:

Don’t derive
KelvinDecorator
directly from
Kelvin
. You should take out the common, required interfaces and put them as a base class (
Character
?), and have
Kelvin
derive from the
Character
superclass.
KelvinDecorator
also derives from the
Character
superclass. This makes the decorator light-weight, and doesn’t hog it with all unnecessary functionality from
Kelvin
’s class.
While decorators are great, they have their disadvantages:

It's very difficult for a developer who doesn’t know this pattern to understand a code written with decorators. You are better than that poor dude as you now know about decorators!
Decorators tend to add a lot of small classes, but that’s something you have to live with.
Any client code that relies on RTTI (i.e., if the client checks for a derived class object type, or in simpler words, whether this is a
Kelvin
object) won't work for decorators. This is because you do not know which decorator layer object we have with us. It could be Spaceman, TRex, Tracer.

Go read the Decorator Design Pattern from GoF book now! This article was intended to make things easy to understand, and to whet your appetite for the GoF chapter on decorators. Search for "Design Patterns: Elements of Reusable Object-Oriented Software",
or the GoF book as it is affectionately called.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: