前端解讀面向切面程式設計(AOP)

瀟湘待雨發表於2018-10-26

前言

物件導向(OOP)作為經典的設計正規化,對於我們來說可謂無人不知,還記得我們入行起始時那句經典的總結嗎-萬事萬物皆物件
是的,基於OOP思想封裝、繼承、多型的特點,我們會自然而然的遵循模組化、元件化的思維來設計開發應用,以到達易維護、可擴充套件、高複用的目的。
既然OOP這麼多優點,那麼經常被大家提起的面向切面程式設計(AOP)是什麼回事呢,下面我們就一起來看一下。

AOP定義

第一步還是要知道aop是什麼,先個來自維基百科的解釋:

面向側面的程式設計(aspect-oriented programming,AOP,又譯作面向方面的程式設計、觀點導向程式設計、剖面導向程式設計)是電腦科學中的一個術語,指一種程式設計範型。
側面的概念源於對物件導向的程式設計的改進,但並不只限於此,它還可以用來改進傳統的函式。

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

tip

確實有點那麼不太清晰,有點亂。不過在亂之前,我們可以選能理解的部分先看一下:

  • 側面(也就是切面) 用來描述分散在物件、類或函式中的橫切關注點
    重點在這,分散在物件中的橫切關注點,可以猜一下是什麼,應該就是不同物件之間公用的部分
  • 側面的概念源於對物件導向的程式設計的改進,它還可以用來改進傳統的函式. AOP 顯然不是OOP的替代品,是OOP的一種補充。
  • 從主關注點中分離出橫切關注點是面向側面的程式設計的核心概念。
    具體到業務專案中來說,主關注點就是業務邏輯了。針對特定領域問題程式碼的呼叫,就是AOP要關注的部分

簡而言之,AOP是針對業務處理過程中的切面(即非業務邏輯部分,例如錯誤處理,埋點,日誌等)進行提取.
它所面對的是處理過程中的某個步驟或階段,以獲得邏輯過程中各部分之間低耦合性的隔離效果(目的是降低耦合)。
具體到實現來說就是通過動態的方式將非主關注點部分插入到主關注點(一般是業務邏輯中)

說了這麼多,可能不太明白,還是一起看程式碼吧。

埋點場景

很普遍的這麼個場景,需要點選按鈕之後進行資訊上報。 假設我們有這麼個logger的工具,可以進行上報:

const logger = console.log
//引入即可使用
logger('按鈕被點選了')
複製程式碼

那麼,我們直接擼起來吧:

const doSomething = ()=>{
    console.log('doSomething')
} 
let clickHandler = ()=>{
   logger('doSomething之前')
   // n行程式碼 
   doSomething() 
   logger('doSomething之後')
   //n 行程式碼
}
複製程式碼

看起來也沒什麼的,簡單粗暴。
如果有30個按鈕,每個業務邏輯不同,都需要埋這個點(假設打點資訊一致)。
我們30個函式裡面,都要手動寫這個方法的話,這也太坑爹了吧。
主要是與業務程式碼嚴重耦合,哪天不小心動了點其他內容,手抖誤刪了,就gg了。
後續維護的時候,簡直噩夢。
仔細看一下,這不就是符合AOP的使用前提嗎,那麼試試AOP吧。

關注點劃分

根據前面提的,可以劃分下關注點。

主關注點 側關注點
業務邏輯(doSomething) 埋點資訊 logger

前面提到AOP關注的是步驟具體到例子來說其實就是插入logger的步驟。
插入時機無非時業務邏輯執行之前或者之後的階段。
具體實現起來也不那麼困難

實現思路

具體到js來說,由於語言本身的特性,天生就具有執行時動態插入邏輯的能力。
重點在於在原函式上增加其他功能並不改變函式本身。

畢竟函式可以接受一切形式的引數,當然函式也不例外了。
當傳入一個函式的時候,我們要對其操作的餘地就很大了,
儲存原函式,然後利用後續引數加上call或apply,就可以達到我們的目的。
此外為了給函式都增加一個屬性,我們在原型上操作就行了。

經典before或者after的實現

網上太多類似實現了,直接看程式碼好了:

// action 即為我們的側關注點,即logger
Function.prototype.after = function (action) {
    //保留當前函式,這裡this指向執行函式即clickHandler
    var func = this;
    // return 被包裝過的函式,這裡就可以執行其他功能了。
    // 並且該方法掛在Function.prototype上,
    // 被返回的函式依然具有after屬性,可以鏈式呼叫
    return function () {
        // 原函式執行,這裡不考慮非同步
        var result = func.apply(this, arguments);
        // 執行之後的操作
        action.apply(this,arguments);
        // 將執行結果返回
        return result;
    };
};
// before 實現類似,只不過執行順序差別而已
Function.prototype.before = function (action) {
    var func = this;
    return function () {
        action.apply(this,arguments);
        return func.apply(this, arguments);
    };
};
複製程式碼

那麼我們使用AOP改造之後的程式碼就如下了:

const doSomething = ()=>{
    console.log('doSomething')
} 
let clickHandler = ()=>{
   // n行程式碼 
   doSomething() 
   //n 行程式碼
}
clickHandler = clickHandler.before(()=>{
     logger('doSomething之前')
}).after(()=>{
     logger('doSomething之後')
})
clickHandler() // 執行結果和預期一致
複製程式碼

到這裡就實現了面向切面程式設計,我們的業務邏輯裡面只管業務本身,側關注點通過這種方式來動態引入,與主邏輯解耦,更加純淨、易於維護。

結束語

到這裡,簡單的AOP就介紹完成了。利用這種模式結合我們js本身的特性,可以嘗試更多的可能。 例如我們react中常見的HOC、es7的裝飾者模式、HOF等,很多時候不得不感嘆大牛們思想的精髓,會讓我們有種頓悟的感覺。本文拋磚引玉,共同學習啦,對自己是總結和提高,更希望能幫助到需要的小夥伴。更多文章請移步我的部落格

參考文章

AllyTeam - 用AOP改善javascript程式碼
深入淺出 Javascript Decorators 和 AOP 程式設計

相關文章