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
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
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
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
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.
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.,
Collapse |
Copy Code
Now, with this base decorator as the super\base class, we create the Kelvin Decorator specific classes:
Collapse |
Copy Code
That’s it! You’ve implemented the Decorator Design Pattern! Now, we let the beauty of basic OOPS do the work!
Collapse |
Copy Code
We’ve done nothing but add one layer of Kelvin on top of another.
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
And we did this by adding new layers of
And last but not least, the output of our program:
Things work great. Now it is easy to add and combine any of
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
D with 1,000 snowballs!!”
You also need to create a new
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!
Don’t derive
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
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.
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
DinoSpacemanbehavior 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-timeto 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,
TracerBazookashould no longer be derived classes of the
Kelvinsuperclass. 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.
KelvinDecoratordoes 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
Kelvinis closed for modification – we never had to modify the
Kelvinclass. 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
KelvinDecoratorat 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
Kelvinclass!
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
WithSusieDecoratorand 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
KelvinDecoratordirectly from
Kelvin. You should take out the common, required interfaces and put them as a base class (
Character?), and have
Kelvinderive from the
Charactersuperclass.
KelvinDecoratoralso derives from the
Charactersuperclass. 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
Kelvinobject) 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.
相关文章推荐
- 装饰设计模式 Learn Decorator Design Pattern in Easy Steps
- 7 Easy Steps to Learn C#: Silverlight C# Compiler and Loading DLLs from a server at runtime in Silverlight
- 6 Easy Steps to Learn Naive Bayes Algorithm (with code in Python)
- 6 Easy Steps to Learn Naive Bayes Algorithm (with code in Python)
- Thinking In Design Pattern——MVP模式演绎
- Learning OpenCV Lecture 2 (Using the Strategy pattern in algorithm design)
- Thinking In Design Pattern——探索SOA在企业应用程序中扮演的角色
- Learn CSS Positioning in Ten Steps
- Easy Steps to Create Storm Project ( Twitter Support) in Eclipse
- Observer Design Pattern in C#!
- pattern design in Game Development
- Template Method Design Pattern in Java
- Thinking In Design Pattern——工厂模式演绎
- 设计模式2--装饰模式(the decorator design pattern)
- Learn CSS Positioning in Ten Steps
- Thinking In Design Pattern——探索SOA在企业应用程序中扮演的角色
- 策略模式 in Java (Strategy Design Pattern)
- From Java to Groovy in a few easy steps
- Think in Design Pattern For software
- Thinking In Design Pattern——工厂模式演绎