Originally post on my blog: What is the decorator pattern? And how to implement it by JavaScript?
Hello you guys! I am Clark. In this post we are going to learn about decorator pattern and implement it by JavaScript!
First of all, my English is not good and hope you will not mind it. If you can correct anything of my post, I will really appreciate for every comment!
Introduction
Decorator pattern is very beautiful pattern, It is implement perfectly Open-Closed Principle. When we finished any class's main function, Except main requirement or logic are changes, we shouldn't modify it. Maybe you are thinking
Why? Why I can't do that?
Okay! Let me talk a simple example below, if I have a class can print something on console of browser:
class Printer {
print(text) {
console.log(text);
}
}
const printerA = new Printer();
printerA.print('something'); // something
And next, the customer said: "Oh! The text's color is too boring! Can you change the text color to yellow?". Sure, just modify as follow:
class Printer {
print(text) {
console.log(`%c${text}`,'color: yellow;');
}
}
When you thinking everything is fine, your customer came again and said: "Hey, can you enlarge size of font? It is too small!". "Umm...Okay!" you said, And modify again as following:
class Printer {
print(text) {
console.log(`%c${text}`,'color: yellow;font-size: 36px;');
}
}
Okay, it is last?
No!
You not only have a one customer, right? So another customer said: "Hey! the size of font too big! Can you change back to original size of font?"
Umm...so what should we do? Maybe we can think some way to resolve the problem:
- Just send a parameter to determine style of print when create object by
new
, I think this is not a good solution, because if your customer become more then yourif
orswitch
will become more. the one thing wrong importantly of the way is classPrinter
just need print, so if you put other logic in it then in the future that will be difficult to modify. - Maybe we can use inheritance, create derived classes for each customer. Yes! that would be awesome, but! if the first customer want to text color shown in red, the second customer want to text color shown in red and set the size of font for
36px
. Now just two customer but your code already repeat twice in two derived classes.
So what should we do?
Decorator pattern would be a awesome option! If you want to do a thing(The thing is print of above example), but before you do that, you must do other things and you don't know how many thing you should do(like set for the text's color and the size of font), than decorator pattern can decorate a thing you want to do!
How to use decorator pattern?
I will refactory above example by decorator pattern!
First we should do something for print, so I will create a new method override original print method, but still call it inside new method, and we can pass style for original print through new method:
class Printer {
print(text, style = '') {
console.log(`%c${text}`, style);
}
}
// decorator method
const yellowStyle = (printer) => ({
...printer,
print: (text) => {
printer.print(text, 'color: yellow;');
}
});
The printer object by Printer
create can decorate with yellowStyle, make text's color become yellow:
So you can according to requirements make a lot of decorator you need, like following:
// decorator methods
const yellowStyle = (printer) => ({
...printer,
print: (text, style = '') => {
printer.print(text, `${style}color: yellow;`);
}
});
const boldStyle = (printer) => ({
...printer,
print: (text, style = '') => {
printer.print(text, `${style}font-weight: bold;`);
}
});
const bigSizeStyle = (printer) => ({
...printer,
print: (text, style = '') => {
printer.print(text, `${style}font-size: 36px;`);
}
});
And through decorator methods compose which you want to display style:
So good! Right? but above example is suboptimal, because I used ...
to get properties of object, but some thing would not exist in the object like methods, the method would store in prototype, so if I just want to through ...
get all things from object, that will be wrong!
For solve this problem, the solution is make a public function to copy another same object include methods of prototype:
const copyObj = (originObj) => {
const originPrototype = Object.getPrototypeOf(originObj);
let newObj = Object.create(originPrototype);
const originObjOwnProperties = Object.getOwnPropertyNames(originObj);
originObjOwnProperties.forEach((property) => {
const prototypeDesc = Object.getOwnPropertyDescriptor(originObj, property);
Object.defineProperty(newObj, property, prototypeDesc);
});
return newObj;
}
Next we need to update content of decorator methods, I talk yellowStyle
as an example:
const yellowStyle = (printer) => {
const decorator = copyObj(printer);
decorator.print = (text, style = '') => {
printer.print(text, `${style}color: yellow;`);
};
return decorator;
};
Check out complete example here.
Following is another suitable situation you can consider using decorator pattern:
If you want to publish a post.
What things you want to do(decorate) before publish?
- Send mail for subscribers
- Push notice to Slack
- Push post on Facebook page
Final words
I think decorator is super good pattern, I like decorator because it like our life, one day we will all die, but before we die, we can make a lot of decorator to decorate our life!
Thanks for you guys read, comments and feedback are super welcome!
Thanks
Photo by Element5 Digital on Unsplash
Top comments (2)
I like this! Looking at the code and not testing it - I'm wondering if you couldn't just do:
I have a method called
decorate
I call often to extend objects (when I can't use Proxy as I run stuff for IE11), it's not quite the same as yours but basically extends existing object with additional properties that won't be saved.This is doing something a bit different because the underlying object isn't copied but remains in place and decorated, probably not with an override.
Thanks for sharing!
This way is a more general than my for action of decorate something, I will try to improve my code by this function you sharing.
By the way next I will learn about proxy pattern 😆