ES7-Decorator-裝飾者模式

裝飾模式

僅僅包裝現有的模塊,使之 「更加華麗」 ,並不會影響原有接口的功能 —— 比如你給手機添加一個外殼罷了,並不影響手機原有的通話、充電等功能;前端

使用 ES7 的 decorator

ES7 中增長了一個 decorator 屬性,它借鑑自 Pythonvue

下面咱們以 鋼鐵俠 爲例講解如何使用 ES7decoratorgit

以鋼鐵俠爲例,鋼鐵俠本質是一我的,只是「裝飾」了不少武器方纔變得那麼 NB,不過再怎麼裝飾他仍是一我的。github

TB1ex47KXXXXXbmXXXXXXXXXXXX-1024-768.jpg

咱們的示例場景是這樣的編程

  • 首先建立一個普通的Man類,它的抵禦值 2,攻擊力爲 3,血量爲 3;
  • 而後咱們讓其帶上鋼鐵俠的盔甲,這樣他的抵禦力增長 100,變成 102;
  • 讓其帶上光束手套,攻擊力增長 50,變成 53;
  • 最後讓他增長「飛行」能力

【Demo 1】對方法的裝飾:裝備盔甲

建立 Man 類:json

class Man{
  constructor(def = 2,atk = 3,hp = 3){
    this.init(def,atk,hp);
  }

  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}`);

// 輸出:當前狀態 ===> 防護力:2,攻擊力:3,血量:3
複製代碼

代碼直接放在 babeljs.io/repl/ 中運行查看結果, 記得勾選SettingEvaluate選項,和 options的選項爲legacybabel

建立 decorateArmour 方法,爲鋼鐵俠裝配盔甲——注意 decorateArmour 是裝飾在方法init上的。app

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;
}

class Man{
  constructor(def = 2,atk = 3,hp = 3){
    this.init(def,atk,hp);
  }

  @decorateArmour
  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,攻擊力:3,血量:3
複製代碼

咱們先看輸出結果,防護力的確增長了 100,看來盔甲起做用了。函數

Decorators 的本質是利用了 ES5Object.defineProperty 屬性,這三個參數實際上是和 Object.defineProperty 參數一致的ui

【Demo 2】裝飾器疊加:增長光束手套

在上面的示例中,咱們成功爲 普通人 增長 「盔甲」 這個裝飾;如今我想再給他增長 「光束手套」,但願額外增長 50 點防護值。

...
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;
}

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;  // 血量
  }
...
}
var tony = new Man();
console.log(`當前狀態 ===> ${tony}`);
//輸出:當前狀態 ===> 防護力:102,攻擊力:53,血量:3
複製代碼

在這裏你就能看出裝飾模式的優點了,它能夠對某個方法進行疊加使用,對原類的侵入性很是小,只是增長一行@decorateLight而已,能夠方便地增刪;(同時還能夠複用)

【Demo 3】對類的裝飾:增長飛行能力

裝飾模式有兩種:純粹的裝飾模式 和 半透明的裝飾模式。

上述的兩個 demo 中所使用的應該是 純粹的裝飾模式,它並不增長對原有類的接口;下面要講 demo 是給普通人增長「飛行」能力,至關於給類新增一個方法,屬於 半透明的裝飾模式,有點兒像適配器模式的樣子。

...

// 3
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;  // 血量
  }
  ...
}
...

console.log(`當前狀態 ===> ${tony}`);
// 輸出:當前狀態 ===> 防護力:102,攻擊力:53,血量:3(技能加成:飛行能力)
複製代碼

做用在方法上的 decorator 接收的第一個參數(target )是類的 prototype;若是把一個 decorator 做用到類上,則它的第一個參數 target 是 類自己。

使用原生 JS 實現裝飾器模式

  • Man 是具體的類,Decorator 是針對 Man 的裝飾器基類
  • 具體的裝飾類 DecorateArmour 典型地使用 prototype 繼承方式 繼承自 Decorator 基類;
  • 基於 IOC(控制反轉)思想 ,Decorator 是接受 Man 類,而不是本身建立 Man 類;
// 首先咱們要建立一個基類
function Man(){

  this.def = 2;
  this.atk = 3;
  this.hp = 3;
}

// 裝飾者也須要實現這些方法,遵照 Man 的接口
Man.prototype={
  toString:function(){
    return `防護力:${this.def},攻擊力:${this.atk},血量:${this.hp}`;
  }
}
// 建立裝飾器,接收 Man 對象做爲參數。
var Decorator = function(man){
  this.man = man;
}

// 裝飾者要實現這些相同的方法
Decorator.prototype.toString = function(){
    return this.man.toString();
}

// 繼承自裝飾器對象
// 建立具體的裝飾器,也是接收 Man 做對參數
var DecorateArmour = function(man){

  var moreDef = 100;
  man.def += moreDef;
  Decorator.call(this,man);

}
DecorateArmour.prototype = new Decorator();

// 接下來咱們要爲每個功能建立一個裝飾者對象,重寫父級方法,添加咱們想要的功能。
DecorateArmour.prototype.toString = function(){
  return this.man.toString();
}

// 注意這裏的調用方式
// 構造器至關於「過濾器」,面向切面的
var tony = new Man();
tony = new DecorateArmour(tony);
console.log(`當前狀態 ===> ${tony}`);
// 輸出:當前狀態 ===> 防護力:102,攻擊力:3,血量:3
複製代碼

經典實現:Logger

經典應用就是 日誌系統 了,那麼咱們也用 ES7 的語法給鋼鐵俠打造一個日誌系統吧。

/** * Created by jscon on 15/10/16. */
let log = (type) => {

  return (target, name, descriptor) => {
    const method = descriptor.value;
    descriptor.value =  (...args) => {
      console.info(`(${type}) 正在執行: ${name}(${args}) = ?`);
      let ret;
      try {
        ret = method.apply(target, args);
        console.info(`(${type}) 成功 : ${name}(${args}) => ${ret}`);
      } catch (error) {
        console.error(`(${type}) 失敗: ${name}(${args}) => ${error}`);
      }
      return ret;
    }
  }
}
class IronMan {
  @log('IronMan 自檢階段')
  check(){
    return '檢查完畢';
  }
  @log('IronMan 攻擊階段')
  attack(){
    return '擊倒敵人';
  }
  @log('IronMan 機體報錯')
  error(){
    throw 'Something is wrong!';
  }
}

var tony = new IronMan();
tony.check();
tony.attack();
tony.error();

// 輸出:
// (IronMan 自檢階段) 正在執行: check() = ?
// (IronMan 自檢階段) 成功 : check() => 檢查完畢
// (IronMan 攻擊階段) 正在執行: attack() = ?
// (IronMan 攻擊階段) 成功 : attack() => 擊倒敵人
// (IronMan 機體報錯) 正在執行: error() = ?
// (IronMan 機體報錯) 失敗: error() => Something is wrong!
複製代碼

Logger 方法的關鍵在於:

  • 首先使用 const method = descriptor.value; 將原有方法提取出來,保障原有方法的純淨;
  • try..catch 語句是 調用 ret = method.apply(target, args);在調用以前以後分別進行日誌彙報;
  • 最後返回 return ret; 原始的調用結果

相關庫

vue中使用裝飾器實現AOP編程

JavaScript中實現AOP,是把一個函數「動態織入」到另外一個函數之中。 首先要構造Functionprototype

//prototype.js
Function.prototype.before = function (beforefn) {
  let _self = this;
  return function () {
    beforefn.apply(this, arguments);
    return _self.apply(this, arguments);
  };
};
Function.prototype.after = function (afterfn) {
  let _self = this;
  return function () {
    let ret = _self.apply(this, arguments);
    afterfn.apply(this, arguments);
    return ret;
  };
};
Function.prototype.around = function (beforefn, afterfn) {
  let _self = this;
  return function () {
    beforefn.apply(this, arguments);
    let ret = _self.apply(this, arguments);
    afterfn.apply(this, arguments);
    return ret;
  };
};
複製代碼

編輯咱們的裝飾器函數

//decorator.js
export const before = function (...args) {
  return function (target, key, descriptor) {
    descriptor.value = descriptor.value.before(() => {
      console.log(`Action-${key} 觸發埋點!`);
    });
  };
};
export const after = function (...args) {
  return function (target, key, descriptor) {
    descriptor.value = descriptor.value.after(() => {
      console.log(`Action-${key} 觸發埋點!`);
    });
  };
};
export const around = function (...args) {
  return function (target, key, descriptor) {
    descriptor.value = descriptor.value.around(() => {
      console.log(`Action-${key} 觸發埋點before!`);
    }, () => {
      console.log(`Action-${key} 觸發埋點after!`);
    });
  };
};
複製代碼

編輯咱們的vue文件

//test.vue
<template>
  <div></div>
</template>
<script>
import { before, after, around } from '@/lib/decorator';
export default {
  data () {

  },
  methods: {
    @before()
    @after()
    @around()
    errorHandle () {
      // 一些共用的異常處理方案
    }
  },
};
</script>
複製代碼

.babelrc文件

{
  "plugins": [
    ["@babel/plugin-proposal-decorators", { "legacy": true }]
  ]
}
複製代碼

摘自

今日圖 羞羞噠

gdfs.gif
相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息