JavaScript设计模式之装饰者模式

2019-06-28185次阅读javascript

装饰者模式的定义:

装饰者(decorator)模式能够在不改变对象自身的基础上,在程序运行期间给对像动态的添加职责。与继承相比,装饰者是一种更轻便灵活的做法。

 

装饰者模式的特点:

可以动态的给某个对象添加额外的职责,而不会影响从这个类中派生的其它对象;

继承的一些缺点:

  • 继承会导致超类和子类之间存在强耦合性,当超类改变时,子类也会随之改变;

  • 超类的内部细节对于子类是可见的,继承常常被认为破坏了封装性

//原始的飞机类
var Plan = function(){};

Plan.prototype.fire = function(){
    console.log('发射普通子弹,');
};

//装饰类
var MissileDecorator = function(plan){
    this.plan = plan;
}

MissileDecorator.prototype.fire = function () {
    this.plan.fire();
    console.log('发射导弹!');
};

var plan = new MissileDecorator( new Plan() );

plan.fire();//发射普通子弹,发射导弹!

 

ES7的装饰器decorator

decorator装饰类

@testable
class MyTestableClass {
  //...
}

function testable(target) {
  target.isTestable = true;
}

MyTestableClass.isTestable // true

@testable就是一个修饰器。它修改了MyTestableClass这个类的行为,为它加上了静态属性isTestable。testable函数的参数target是MyTestableClass类本身

如果觉得一个参数不够用,可以在修饰器外面再封装一层函数

function testable(isTestable) {
  return function(target) {
    target.isTestable = isTestable;
  }
}

@testable(true)
class MyTestableClass {}
MyTestableClass.isTestable // true

@testable(false)
class MyClass {}
MyClass.isTestable // false

上面代码中,修饰器testable可以接受参数,这就等于可以修改修饰器的行为

注意,修饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,修饰器能在编译阶段运行代码。也就是说,修饰器本质就是编译时执行的函数

前面的例子是为类添加一个静态属性,如果想添加实例属性,可以通过目标类的prototype对象操作

function testable(target) {
  target.prototype.isTestable = true;
}

@testableclass 
MyTestableClass {}
let obj = new MyTestableClass();
obj.isTestable // true

上面代码中,修饰器函数testable是在目标类的prototype对象上添加属性,因此就可以在实例上调用。修饰类重写第一示例:

function MissileDecorator(target){
    let method = target.prototype.fire;
    target.prototype.fire = function(...args){
        method.apply(target.prototype,...args);
        onsole.log('发射导弹!');
    }

}
//修饰类
@MissileDecorator
class Plan{//原始的飞机类
    fire(){
        console.log('发射普通子弹,');
    }
}

new Plan().fire();//发射普通子弹,发射导弹!

decorator装饰方法

修饰器不仅可以修饰类,还可以修饰类的属性方法

class Person {
  @readonly
  name() { 
    return `${this.first} ${this.last}` 
  }
}

上面代码中,修饰器readonly用来修饰“类”的name方法。

修饰器函数readonly一共可以接受三个参数

function readonly(target, name, descriptor){
  // descriptor对象原来的值如下
  // {
  //   value: specifiedFunction,
  //   enumerable: false,
  //   configurable: true,
  //   writable: true
  // };
  descriptor.writable = false;
  return descriptor;
}

readonly(Person.prototype, 'name', descriptor);
// 类似于
Object.defineProperty(Person.prototype, 'name', descriptor);

修饰器第一个参数是类的原型对象,上例是Person.prototype,修饰器的本意是要“修饰”类的实例,但是这个时候实例还没生成,所以只能去修饰原型(这不同于类的修饰,那种情况时target参数指的是类本身);第二个参数是所要修饰的属性名,第三个参数是该属性的描述对象。修饰器Decorators的本质是利用了ES5的Object.defineProperty属性,这三个参数其实是和Object.defineProperty参数一致的。修饰方法重写第一示例:

function MissileDecorator(target,name,descriptor){
    let method = descriptor.value;
    descriptor.value = function(...args){
        method.apply(target,...args);
        console.log('发射导弹!');
    }
}

//原始的飞机类
class Plan{
    //装饰方法
    @MissileDecorator
    fire(){
        console.log('发射普通子弹,');
    }
}

new Plan().fire();//发射普通子弹,发射导弹!

淘宝前端团钢铁侠示例

  • 首先创建一个普通的Man类,它的抵御值 2,攻击力为 3,血量为 3;

  • 然后我们让其带上钢铁侠的盔甲,这样他的抵御力增加 100,变成 102;

  • 让其带上光束手套,攻击力增加 50,变成 53;

  • 最后让他增加“飞行”能力

//装配盔甲(类方法修饰)
function decorateArmour(target, key, descriptor){
    const method = descriptor.value;
    let moreDef = 100;
    let ret;

    descriptor.value = (...args)=>{
      args[0] += moreDef;
      ret = method.apply(target, args);
      return ret;
    }
    return descriptor;
}

//光束手套(类方法修饰)
function decorateLight(target, key, descriptor) {
    const method = descriptor.value;
    let moreAtk = 50;
    let ret;
    descriptor.value = (...args)=>{
      args[1] += moreAtk;
      ret = method.apply(target, args);
      return ret;

    }
    return descriptor;
}

//增加飞行能力(修饰类)
function addFly(canFly){
    return function(target){
      target.canFly = canFly;
      let extra = canFly ? '(技能加成:飞行能力)' : '';
      let method = target.prototype.toString;
      target.prototype.toString = (...args)=>{
         return method.apply(target.prototype,args) + extra;
      }
      return target;
    }
}

@addFly(true)
class Man{

   constructor(def = 2,atk = 3,hp = 3){
      this.init(def,atk,hp);
   }
   
   @decorateArmour
   @decorateLight 
   init(def,atk,hp){
       this.def = def; // 防御值
       this.atk = atk;  // 攻击力
       this.hp = hp;  // 血量
   }
   
   toString(){
        return `防御力:${this.def},攻击力:${this.atk},血量:${this.hp}`;
   }

}

var tony = new Man();
console.log(`当前状态 ===> ${tony}`);//当前状态 ===> 防御力:102,攻击力:53,血量:3(技能加成:飞行能力)

装饰模式有两种:纯粹的装饰模式和半透明的装饰模式。

上述修饰类方法应该是纯粹的装饰模式,它并不增加对原有类的接口;修饰类相当于给类新增一个方法,属于半透明的装饰模式。

推荐阅读:

http://taobaofed.org/blog/2015/11/16/es7-decorator/

https://github.com/zhiqiang21/blog/issues/17

http://es6.ruanyifeng.com/#docs/decorator

上一篇: IOS下input获取焦点,弹出软键盘导致页面内容被顶上去  下一篇: TypeScript中的类型断言  

JavaScript设计模式之装饰者模式相关文章