裝飾器模式(Decorator Pattern)允許向一個現有的對象動態添加新的功能,同時又不改變其結構。相比JavaScript中通過雞肋的繼承來給對象增加功能來說,裝飾器模式相比生成子類更為靈活。
裝飾模式和適配器模式都是 包裝模式 (Wrapper Pattern),它們都是通過封裝其他對象達到設計的目的的,但是它們的形態有很大區別。
適配器模式我們使用的場景比較多,比如連接不同數據庫的情況,你需要包裝現有的模塊接口,從而使之適配數據庫 —— 好比你手機使用轉接口來適配插座那樣;
裝飾模式不一樣,僅僅包裝現有的模塊,使之 “更加華麗” ,並不會影響原有接口的功能 —— 好比你給手機添加一個外殼罷了,並不影響手機原有的通話、充電等功能;
下面通過一個實例介紹裝飾器模式的使用方法。孫悟空一出生雖然不同尋常,但也是只普通的猴子
- 首先創建一個普通猴子對象
function Monkey() {
console.log("很久很久以前,海邊的一塊石頭,吸日月之精華,集天地之靈氣,突然有一天,石頭崩裂,從里面竄出一只潑猴!");
}
Monkey.prototype = {
toString: function () {
console.log('我是潑猴');
},
attack: function () {
console.log("猴拳出擊");
},
defend: function () {
console.log("我跳,我跳,我跳跳跳");
}
}
- 接着創建一個裝飾器'類'
// 創建裝飾器,接收 monkey 對象作為參數。
var Decorator = function (monkey) {
this.monkey = monkey;
}
// 裝飾者要實現這些相同的方法
Decorator.prototype = {
toString: function () {
this.monkey.toString();
},
attack: function () {
this.monkey.attack();
},
defend: function () {
this.monkey.defend();
}
}
- 創建具體的裝飾器對象
接下來我們要為每一個功能創建一個裝飾者對象,重寫父級方法,添加我們想要的功能。該裝飾器對象繼承自裝飾器'類',也接收monkey實例作為參數。
1.找菩提祖師學72變
var Decorate72Changes = function (monkey) {
Decorator.call(this, monkey);
console.log("學會72變");
}
Decorate72Changes.prototype = new Decorator();
//重寫父類方法
Decorate72Changes.prototype.toString = function () {
console.log("我是美猴王");
}
Decorate72Changes.prototype.defend = function () {
this.monkey.defend();
console.log("俺變,俺變,俺變變變");
}
2.找東海龍王要金箍棒
var DecorateGoldenCudgel = function (monkey) {
Decorator.call(this, monkey);
console.log("獲得金箍棒");
}
DecorateGoldenCudgel.prototype = new Decorator();
//重寫父類方法
DecorateGoldenCudgel.prototype.toString = function () {
console.log("我是齊天大聖,孫悟空");
}
DecorateGoldenCudgel.prototype.attack = function () {
this.monkey.attack();
console.log("吃我一棒");
}
3.太上老君煉丹爐練就火眼金睛
var DecorateSharpEyes = function (monkey) {
Decorator.call(this, monkey);
console.log("獲得火眼金睛");
}
DecorateSharpEyes .prototype = new Decorator();
//重寫父類方法
DecorateSharpEyes .prototype.toString = function () {
console.log("我是孫行者");
}
DecorateSharpEyes.prototype.findMonster = function () {
console.log("妖怪,哪里跑");
}
- 使用裝飾器裝飾潑猴
var monkey=new Monkey();//很久很久以前,海邊的一塊石頭,吸日月之精華,集天地之靈氣,突然有一天,石頭崩裂,從里面竄出一只潑猴!
monkey.toString();//我是潑猴
monkey.attack();//猴拳出擊
monkey.defend();//我跳,我跳,我跳跳跳
var monkeyKing=new Decorate72Changes(monkey);//學會72變
monkeyKing.toString();//我是美猴王
monkeyKing.attack();//猴拳出擊
monkeyKing.defend();//我跳,我跳,我跳跳跳;俺變,俺變,俺變變變
var monkeyGod=new DecorateGoldenCudgel(monkeyKing);//獲得金箍棒
monkeyGod.toString();//我是齊天大聖,孫悟空
monkeyGod.attack();//猴拳出擊;吃我一棒
monkeyGod.defend();//我跳,我跳,我跳跳跳;俺變,俺變,俺變變變
var monkeySun=new DecorateSharpEyes(monkeyGod);//獲得火眼金睛
monkeySun.toString();//我是孫行者
monkeySun.findMonster();//妖怪,哪里跑
裝飾者模式是保持對象功能差異性的一種很好的方式,從長遠來看有助於提高代碼的可維護性。
在 ES6 中增加了對類對象的相關定義和操作(比如 class 和 extends ),這就使得我們在多個不同類之間共享或者擴展一些方法或者行為的時候,變得並不是那么優雅。這個時候,我們就需要一種更優雅的方法來幫助我們完成這些事情。
裝飾器最早是在 python 2.4 里增加的功能,它的主要作用與裝飾者模式類似,是給一個已有的方法或類擴展一些新的行為,而不是去直接修改它本身。
def decorator(f):
print "my decorator"
return f
@decorator
def myfunc():
print "my function"
myfunc()
# my decorator
# my function
這里的 @decorator 就是我們說的裝飾器。上面的代碼中利用裝飾器給目標方法執行前打印出了一行文本,並且並沒有對原方法做任何的修改。ES7中的裝飾器借鑒了Python的思想,實現方法也類似
function sayYourName(target,key,descriptor){
descriptor.value=()=>{
console.log("我是潑猴");
}
console.log("報上名來")
return descriptor;
}
class Monkey{
@sayYourName
toString(){}
}
monkey=new Monkey();
monkey.toString();
//報上名來
//我是潑猴
ES6+的這種語法實際上是一種語法糖,而實際上當我們給一個類添加一個屬性的時候,會調用到 Object.defineProperty 這個方法,它會接受三個參數:target 、name 和 descriptor ,所以上面的代碼在未添加裝飾器時解析成ES5是這樣的:
function Monkey() {}
Object.defineProperty(Monkey.prototype, "toString", {
value: function() {},
enumerable: true,
configurable: true,
writable: true
});
在加上裝飾器后,會在執行第二步的時候安裝上這個裝飾器(可以看成把這個步驟分成了兩小步),通過執行裝飾器函數返回一個descriptor屬性描述符
function sayYourName(target,key,descriptor){
descriptor.value=()=>{
console.log("我是潑猴");
}
console.log("報上民來")
return descriptor;
}
function Monkey() {}
var descriptor={
value:function(){},
enumerable: true,
configurable: true,
writable: true
}
// 裝飾器工廠函數 接收的參數與 Object.defineProperty 一致
descriptor = sayYourName(Monkey.prototype, 'toString', descriptor)
Object.defineProperty(Monkey.prototype, "toString", descriptor);
monkey=new Monkey();
monkey.toString();
當裝飾器作用於類屬性方法時,參數中的target
為類的原型,裝飾器還可以作用於類本身,此時的target
參數是類本身。
function isAnimal(target) {
target.isAnimal = true;
}
@isAnimal
class Monkey{
toString(){}
}
console.log(Monkey.isAnimal); // true
裝飾器函數還可以是一個工廠函數,可以傳遞參數
function animal(name) {
return function (target) {
target.call = name;
}
}
@animal("猴子")
class Monkey { }
@animal("豬")
class Pig { }
console.log(Monkey.call);
console.log(Pig.call);
有了前面的基礎,可以使用ES7的decorator實現一開始的裝飾器模型。
function decorate72Changes(target, key, descriptor) {
const method = descriptor.value;
descriptor.value = ()=>{
method.apply(target);
console.log("俺變,俺變,俺變變變");
}
console.log("學會72變");
return descriptor;
}
function decorateGoldenCudgel(target, key, descriptor) {
const method = descriptor.value;
descriptor.value = ()=>{
method.apply(target);
console.log("吃我一棒");
}
console.log("獲得金箍棒");
return descriptor;
}
function decorateSharpEyes(target, key, descriptor) {
descriptor.value = ()=>{
console.log("妖怪,哪里跑");
}
console.log("獲得火眼金眼");
return descriptor;
}
function decorateToString(target, key, descriptor) {
descriptor.value = ()=>{
console.log("我是孫行者");
}
return descriptor;
}
class Monkey {
constructor(){
console.log("很久很久以前,海邊的一塊石頭,吸日月之精華,集天地之靈氣,突然有一天,石頭崩裂,從里面竄出一只潑猴!");
}
@decorateToString
toString(){
console.log('我是潑猴');
}
@decorateGoldenCudgel
attack(){
console.log("猴拳出擊");
}
@decorate72Changes
defend(){
console.log("我跳,我跳,我跳跳跳");
}
@decorateSharpEyes
findMonster(){}
}
monkeySun=new Monkey();
monkeySun.defend();
monkeySun.attack();
monkeySun.findMonster();
要運行上述代碼還需要babel轉譯
1.安裝基礎依賴包
npm i babel-plugin-transform-decorators-legacy babel-register --save-dev
安裝:
babel-plugin-transform-decorators-legacy
babel-register
transform-decorators-legacy:
是第三方插件,用於支持decorators
babel-register:
用於接入node api
運行方法一:命令行操作
babel --plugins transform-decorators-legacy input.js>input.es5.js
然后直接運行es5的代碼
運行方法二:require hook
require('babel-register')({
plugins: ['transform-decorators-legacy']
});
require("./input.js")