装饰器模式&&ES7 Decorator 装饰器

装饰器模式(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")
posted @ 2017-08-20 21:52  FeMiner  阅读(4668)  评论(1编辑  收藏  举报