2018-07-10裝飾器

裝飾器模式

裝飾模式和適配器模式都是 包裝模式 (Wrapper Pattern)骏令,它們都是通過封裝其他對象達(dá)到設(shè)計的目的的则果,但是它們的形態(tài)有很大區(qū)別岳掐。

  • 適配器模式我們使用的場景比較多(繼承方案),比如連接不同數(shù)據(jù)庫的情況哮奇,你需要包裝現(xiàn)有的模塊接口膛腐,從而使之適配數(shù)據(jù)庫 —— 好比你手機使用轉(zhuǎn)接口來適配插座那樣;

  • 裝飾模式不一樣屏镊,僅僅包裝現(xiàn)有的模塊依疼,使之 “更加華麗” 痰腮,并不會影響原有接口的功能 —— 好比你給手機添加一個外殼罷了而芥,并不影響手機原有的通話、充電等功能膀值;


    更多區(qū)別參見:設(shè)計模式——裝飾模式(Decorator)

裝飾模式場景 —— 面向 AOP 編程

裝飾模式經(jīng)典的應(yīng)用是 AOP 編程棍丐,比如“日志系統(tǒng)”误辑,日志系統(tǒng)的作用是記錄系統(tǒng)的行為操作,它在不影響原有系統(tǒng)的功能的基礎(chǔ)上增加記錄環(huán)節(jié) —— 好比你佩戴了一個智能手環(huán)歌逢,并不影響你日常的作息起居巾钉,但你現(xiàn)在卻有了自己每天的行為記錄。

更加抽象的理解秘案,可以理解為給數(shù)據(jù)流做一層filter砰苍,因此 AOP 的典型應(yīng)用包括 安全檢查、緩存阱高、調(diào)試赚导、持久化等等〕嗑可參考Spring aop 原理及各種應(yīng)用場景 吼旧。

我們知道,面向?qū)ο蟮奶攸c是繼承未舟、多態(tài)和封裝圈暗。而封裝就要求將功能分散到不同的對象中去,這在軟件設(shè)計中往往稱為職責(zé)分配裕膀。實際上也就是說员串,讓不同的類設(shè)計不同的方法。這樣代碼就分散到一個個的類中去了昼扛。這樣做的好處是降低了代碼的復(fù)雜程度昵济,使類可重用。
但是人們也發(fā)現(xiàn)野揪,在分散代碼的同時访忿,也增加了代碼的重復(fù)性。什么意思呢斯稳?比如說海铆,我們在兩個類中,可能都需要在每個方法中做日志挣惰。按面向?qū)ο蟮脑O(shè)計方法卧斟,我們就必須在兩個類的方法中都加入日志的內(nèi)容。也許他們是完全相同的憎茂,但就是因為面向?qū)ο蟮脑O(shè)計讓類與類之間無法聯(lián)系珍语,而不能將這些重復(fù)的代碼統(tǒng)一起來。
也許有人會說竖幔,那好辦啊板乙,我們可以將這段代碼寫在一個獨立的類獨立的方法里,然后再在這兩個類中調(diào)用。但是募逞,這樣一來蛋铆,這兩個類跟我們上面提到的獨立的類就有耦合了,它的改變會影響這兩個類放接。那么刺啦,有沒有什么辦法,能讓我們在需要的時候纠脾,隨意地加入代碼呢玛瘸?這種在運行時,動態(tài)地將代碼切入到類的指定方法苟蹈、指定位置上的編程思想就是面向切面的編程捧韵。

什么是裝(修)飾器

裝飾器Decorator是在es2016(es7)中新加入的提案,前端中的裝飾器的最早在Angular 2+中使用汉操,當(dāng)然再来,在當(dāng)時Angular中,裝飾器因TypeScript能使用磷瘤。

簡單來說芒篷,裝飾器是用一個代碼包裝另一個代碼的簡單方式。裝飾器是一個函數(shù)采缚,主要來修改類的或者類的方法行為针炉,只在類的范疇內(nèi)有用

如何使用JavaScript裝飾器

JavaScript中裝飾器使用特殊的語法,使用@作為標(biāo)識符扳抽,且放置在被裝飾代碼之前篡帕。

下面是一個簡單的裝飾器

function testable(target) {
    target.UserName = 'linxiaodong';
}
@testable
class Test{
}
console.log(Test.UserName); //linxiaodong 

給Test類天津愛了一個靜態(tài)屬性UserName.

裝飾器的作用

裝飾器的本質(zhì)只是一種語法糖而已,編譯時會把注解的代碼翻譯成我們熟悉的那種形式贸呢。

詳解

作用在方法上的 decorator 接收的第一個參數(shù)(target )是類的 prototype镰烧;如果把一個 decorator 作用到類上,則它的第一個參數(shù) target 是 類本身楞陷。

對類的修飾

上面的就是一個對類的修飾怔鳖,但是如果需要參數(shù)的話,我們可以在修飾器外面再封裝一層函數(shù)固蛾。

function testableWrap(username){
    return function testable(target) {
        target.UserName = username;
    }
}

@testableWrap('linxiaodong')
class Test{
}
console.log(Test.UserName); //linxiaodong 

上面是對類添加靜態(tài)方法结执,如果要給類的實例對象添加方法,可以通過prototype實現(xiàn)艾凯。

 function mixins(...list) {
    return function (target) {
      Object.assign(target.prototype, ...list)
    }
  }
  
  const Foo = {
    foo() { console.log('foo') }
  };
  
  @mixins(Foo)
  class MyClass {}
  
  let obj = new MyClass();
  obj.foo() // 'foo'

上面代碼通過修飾器mixins献幔,把Foo對象的方法添加到了MyClass的實例上面≈菏可以用Object.assign()模擬這個功能蜡感。

對類中方法的修飾

class Person {
    @readonly
    testFn() { return `${this.first} ${this.last}` }
}
console.log('Person.prototype',Person.prototype);
function readonly(target, name, descriptor){
    console.log('target',target);
    console.log('name',name); // testFn
    console.log('descriptor',descriptor);
    descriptor.writable = false;
    return descriptor;
}

修飾器第一個參數(shù)是類的原型對象,上例是Person.prototype,修飾器的本意是要“修飾”類的實例铸敏,但是這個時候?qū)嵗€沒生成,所以只能去修飾原型(這不同于類的修飾悟泵,那種情況時target參數(shù)指的是類本身)杈笔;第二個參數(shù)是所要修飾的屬性名,第三個參數(shù)是該屬性的描述對象糕非。

修飾器不能用于函數(shù)

var counter = 0;
var add = function () {
  counter++;
};
@add
function foo() {
}
//SyntaxError: Leading decorators must be attached to a class declaration

由于存在函數(shù)提升蒙具,使得修飾器不能用于函數(shù)。類是不會提升的朽肥,所以就沒有這方面的問題禁筏。

另一方面,如果一定要修飾函數(shù)衡招,可以采用高階函數(shù)的形式直接執(zhí)行篱昔。

function doSomething(name) {
    console.log('Hello, ' + name);
  }
  
function loggingDecorator(wrapped) {
    return function() {
      console.log('Starting');
      const result = wrapped.apply(this, arguments);
      console.log('Finished');
      return result;
    }
  }
  
const myDecorator = loggingDecorator(doSomething);
class Test{
    @myDecorator('linxiaodong')
    speak(){
        console.log('speakFn');
    }
}
// Starging
// Hello,linxiaodong
// Finished

常用的裝飾器

core-decorators.js是一個第三方模塊,提供了幾個常見的修飾器始腾,通過它可以更好地理解修飾器州刽。比如Angular2+中就內(nèi)置了這些裝飾器。

(1)@autobind
autobind修飾器使得方法中的this對象浪箭,綁定原始對象穗椅。

import { autobind } from 'core-decorators';

class Person {
  @autobind
  getPerson() {
    return this;
  }
}

let person = new Person();
let getPerson = person.getPerson;

getPerson() === person;
// true

使用原生 JS 實現(xiàn)裝飾器模式

// 首先我們要創(chuàng)建一個基類
function Man(){

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

// 裝飾者也需要實現(xiàn)這些方法,遵守 Man 的接口
Man.prototype={
 toString:function(){
 return `防御力:$,攻擊力:$,血量:$`;
 }
}
// 創(chuàng)建裝飾器奶栖,接收 Man 對象作為參數(shù)匹表。
var Decorator = function(man){
 this.man = man;
}

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

// 繼承自裝飾器對象
// 創(chuàng)建具體的裝飾器,也是接收 Man 作對參數(shù)
var DecorateArmour = function(man){

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

}
DecorateArmour.prototype = new Decorator();

// 接下來我們要為每一個功能創(chuàng)建一個裝飾者對象宣鄙,重寫父級方法袍镀,添加我們想要的功能。
DecorateArmour.prototype.toString = function(){
 return this.man.toString();
} 

// 注意這里的調(diào)用方式
// 構(gòu)造器相當(dāng)于“過濾器”冻晤,面向切面的
var tony = new Man();
tony = new DecorateArmour(tony);
console.log(`當(dāng)前狀態(tài) ===> $`);
// 輸出:當(dāng)前狀態(tài) ===> 防御力:102,攻擊力:3,血量:3

下面是一個使用原生和使用裝飾器的寫法對比

// function decorateArmour(target, key, descriptor) {
//     const method = descriptor.value;
//     console.log(method);
//     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(`當(dāng)前狀態(tài) ===> ${tony.toString()}`); 


// 使用原生來寫
// 首先我們要創(chuàng)建一個基類
function Man(){
    this.def = 2;
    this.atk = 3; 
    this.hp = 3;
   }
   
   // 裝飾者也需要實現(xiàn)這些方法流椒,遵守 Man 的接口
   Man.prototype={
    toString:function(){
        return `防御力:${this.def},攻擊力:${this.atk},血量:${this.hp}`;
    }
   }
   // 創(chuàng)建裝飾器,接收 Man 對象作為參數(shù)明也。
   var Decorator = function(man){
    this.man = man;
   }
   
   // 裝飾者要實現(xiàn)這些相同的方法
   Decorator.prototype.toString = function(){
    return this.man.toString();
   }
   
   // 繼承自裝飾器對象
   // 創(chuàng)建具體的裝飾器宣虾,也是接收 Man 作對參數(shù)
   var DecorateArmour = function(man){
   
    var moreDef = 100;
    man.def += moreDef;
    Decorator.call(this,man);
   
   }
   DecorateArmour.prototype = new Decorator();
   
   // 接下來我們要為每一個功能創(chuàng)建一個裝飾者對象,重寫父級方法温数,添加我們想要的功能绣硝。
   DecorateArmour.prototype.toString = function(){
    return this.man.toString();
   } 
   
   // 注意這里的調(diào)用方式
   // 構(gòu)造器相當(dāng)于“過濾器”,面向切面的
   var tony = new Man();
   tony = new DecorateArmour(tony);
   
   console.log( `防御力:${tony.toString()}`);
   // 輸出:當(dāng)前狀態(tài) ===> 防御力:102,攻擊力:3,血量:3

參考文獻(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市丘喻,隨后出現(xiàn)的幾起案子脯宿,更是在濱河造成了極大的恐慌,老刑警劉巖泉粉,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件连霉,死亡現(xiàn)場離奇詭異,居然都是意外死亡嗡靡,警方通過查閱死者的電腦和手機窘面,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來叽躯,“玉大人财边,你說我怎么就攤上這事〉闫铮” “怎么了酣难?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長黑滴。 經(jīng)常有香客問我憨募,道長,這世上最難降的妖魔是什么袁辈? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任菜谣,我火速辦了婚禮,結(jié)果婚禮上晚缩,老公的妹妹穿的比我還像新娘尾膊。我一直安慰自己,他們只是感情好荞彼,可當(dāng)我...
    茶點故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布冈敛。 她就那樣靜靜地躺著,像睡著了一般鸣皂。 火紅的嫁衣襯著肌膚如雪抓谴。 梳的紋絲不亂的頭發(fā)上暮蹂,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天,我揣著相機與錄音癌压,去河邊找鬼仰泻。 笑死,一個胖子當(dāng)著我的面吹牛滩届,可吹牛的內(nèi)容都是我干的集侯。 我是一名探鬼主播,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼丐吓,長吁一口氣:“原來是場噩夢啊……” “哼浅悉!你這毒婦竟也來了趟据?” 一聲冷哼從身側(cè)響起券犁,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎汹碱,沒想到半個月后粘衬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡咳促,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年稚新,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片跪腹。...
    茶點故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡褂删,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出冲茸,到底是詐尸還是另有隱情屯阀,我是刑警寧澤,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布轴术,位于F島的核電站难衰,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏逗栽。R本人自食惡果不足惜盖袭,卻給世界環(huán)境...
    茶點故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望彼宠。 院中可真熱鬧鳄虱,春花似錦、人聲如沸凭峡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽想罕。三九已至悠栓,卻和暖如春霉涨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背惭适。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工笙瑟, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人癞志。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓往枷,卻偏偏與公主長得像,于是被迫代替她去往敵國和親凄杯。 傳聞我的和親對象是個殘疾皇子错洁,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,689評論 2 354

推薦閱讀更多精彩內(nèi)容