『設計模式』高階函式實現 AOP

Weiting Zhang發表於2017-11-20

一篇記錄了自己思考的讀書筆記。
部落格:Murphy 的部落格
知乎:@Murphy

面向側面的程式設計(aspect-oriented programming,AOP,又譯作面向方面的程式設計、觀點導向程式設計、剖面導向程式設計)是電腦科學中的一個術語,指一種程式設計範型。該範型以一種稱為側面(aspect,又譯作方面)的語言構造為基礎,側面是一種新的模組化機制,用來描述分散在物件、類或函式中的橫切關注點(crosscutting concern)。

從程式設計的角度看,在執行時,動態地將程式碼切入到類的指定方法、指定位置的程式設計思想就是面向切面程式設計

AOP 的主要作用是把一些跟核心業務邏輯模組無關的功能抽離出來,這些跟業務邏輯無關的功能通常包括日誌統計、安全控制、異常處理等。這樣做的好處首先是可以保持業務邏輯模組的純淨和高內聚性,其次是可以很方便地複用日誌統計等功能模組。

從主關注點中分離出橫切關注點是面向側面的程式設計(aspect-oriented programming)的核心概念。分離關注點使得解決特定領域問題的程式碼從業務邏輯中獨立出來,業務邏輯的程式碼中不再含有針對特定領域問題程式碼的呼叫,業務邏輯同特定領域問題的關係通過側面來封裝、維護,這樣原本分散在在整個應用程式中的變動就可以很好的管理起來。

在 Java 語言中,可以通過反射和動態代理實現 AOP 技術,而在 JavaScript 中,有一種非常巧妙的方法實現。我們先來看一下想要達到的效果:

原來的函式長這樣(可以把它看作核心業務邏輯):

var func = function() {
      console.log(2);
}複製程式碼

現在呢,想改造一下這個函式,把某些希望在把這個函式之前執行和之後執行的函式“動態織入”到核心函式中,像這樣:

func = func.before(function() {
      // 在 func 執行之前執行的函式
}).after(function() {
      // 在 func 執行之後執行的函式
})複製程式碼

然後再執行

func();複製程式碼

時,能夠依次執行 before() 傳入的函式,func() 原來的內容,最後是 after() 傳入的函式。怎麼做到呢?

首先,函式能夠鏈式執行下去意味著任何函式(或者其原型鏈上)都有對 beforeafter 的定義,因此可以擴充函式的建構函式(Function) 的原型(prototype)。然後為了能按照我們希望的順序執行,需要調整 func() 本身的內容在執行過程中的順序。我們知道在 JavaScript 中,除了直接執行一個函式外,還可以使用 call 或者 apply 執行,比如:

var foo = function(str) {
      console.log(str);
}
foo("hello"); // hello
foo.call(this, "hello"); // hello
foo.apply(this, ["hello"]); // hello複製程式碼

所以呢,是不是可以這樣子,為了在執行核心函式 func 之前動態織入 before 中的函式,可以先儲存對 func 的引用,並在之後返回一個規定好執行順序的裝飾後的函式。像這樣:

Function.prototype.before = function(beforefn) {
      var _self = this; // 儲存對原函式的引用
      return function() {
            beforefn.apply(this, arguments);
            _self.apply(this, arguments);
      }
}複製程式碼

注意這句 _self = this 的作用,因為我們呼叫 before 時會作為核心函式的方法呼叫,所以第一個 this 會是核心函式本身;而在返回的函式中,this 就無所謂了,它指向的是全域性變數,在瀏覽器中的話就是 window

來驗證一下:

func = func.before(function() {
    console.log(1);
})

func(); // 1  2複製程式碼

Success √

所以 after 的寫法也類似:

Function.prototype.after = function(afterfn) {
      var _self = this;
      return function() {
            _self.apply(this, arguments);
            afterfn.apply(this, arguments);
      }
}複製程式碼

還是一樣測試一下:

func = func.after(function() {
    console.log(3);
})

func(); // 2  3複製程式碼

也可以鏈式呼叫起來:

func = func.before(function() {
    console.log(1);
}).after(function() {
    console.log(3);
})

func(); // 1  2  3複製程式碼

這種使用 AOP 的方式來給函式動態的新增職責,也是 JavaScript 語言中一種特別和巧妙的裝飾者模式實現。

參考:

JavaScript 設計模式與開發實踐

維基百科-面向側面的程式設計

相關文章