面向切面編程

Q: 什么是食物?
A: 食物通常以碳水化合物、脂肪、蛋白質(zhì)或水構(gòu)成,能夠借由進(jìn)食或是飲用為人類或者生物提供營(yíng)養(yǎng)或愉悅的物質(zhì)。
   食物的來源可以是植物、動(dòng)物或者其他界的生物,例如真菌,亦或發(fā)酵產(chǎn)品像是酒精。
   生物攝取食物后,被生物的細(xì)胞同化,提供能量,維持生命及刺激成長(zhǎng)。 

前段時(shí)間討論到Java中的回調(diào)函數(shù)時(shí),提到了面向切面(Aspect Oriented Programming)編程。它是一種程序設(shè)計(jì)思維,常常與面向?qū)ο螅∣bject Oriented Programming)編程相比較,但是也常常被單獨(dú)拎出來討論。

OOP的誕生源自于人類與生俱來的分類抽象能力,回想一下,讀小學(xué)的你,是否就能輕而易舉地分辨“零食”和“正餐”的區(qū)別呢?零食美味而充滿誘惑,但是對(duì)于你來說昂貴,而且吃太多存在挨罵的風(fēng)險(xiǎn),相對(duì)的,正餐則略顯的無聊和平淡,但是在你餓了的時(shí)候往往能讓你的肚子不會(huì)咕咕叫。

于是自然而然你就可以將它們的共同點(diǎn)抽象出來,比如它們可以被食用,但是它們的味道、價(jià)格、食用時(shí)間乃至“副作用”之類的都不太一樣。它們中相同的屬性,就可以分配給一個(gè)只對(duì)這些相同的屬性敏感的父元素。而不同的部分,就單獨(dú)分配給父元素下屬的不同的子元素。這樣一來,子元素就不光含有自己的屬性,也擁有了父元素的所有屬性。

所以現(xiàn)在名詞解釋對(duì)你來說,會(huì)不會(huì)變得更容易呢?

面向切面

現(xiàn)在你知道了,事物中對(duì)于某些相同屬性的抽象,構(gòu)成了面向?qū)ο蟮幕?/p>

來寫一個(gè)簡(jiǎn)單的OOP的例子

    // JavaScript ES6

    class Person {
        constructor(name) {
            this.name = name;
        }
        greeting() {
            console.log(`hello, my name is ${this.name}, `);
        }
    }

    class Engineer extends Person {
        constructor(name, level) {
            super(name);
            this.level = level;
        }
        // override
        greeting() {
            super.greeting();
            console.log(`a ${this.level} Engineer.`);
        }
    }

    class Architect extends Engineer {
        constructor(name, level) {
            super(name, level);
        }
        design(work) {
            console.log(`right now, i\`m ${work}.`);
        }
        playGames(game, platform) {
            console.log(`${this.name} is playing ${game} on ${platform} now.`);
        }
    }

    let ted = new Architect('Ted', 'Junior');
    ted.greeting();
    ted.design('painting some blueprints');

    >>> 
    hello, my name is Ted,
    a Junior Engineer.
    right now, i'm painting some blueprints.

侵入式切面

假設(shè)我們想要在對(duì)象 Person 的所有方法執(zhí)行前,加入一段邏輯。我們有什么好辦法呢?

代理函數(shù)是個(gè)很好的選擇,我們?cè)谶M(jìn)行具體的業(yè)務(wù)調(diào)用時(shí),直接調(diào)用一個(gè)代理函數(shù)就行了。


    function before(person, fn, ...args) {
        console.log('something running before');
        return fn.apply(person, args);
    }

函數(shù) before,將我們要調(diào)用的函數(shù)包裹起來,然后我們只需要這樣調(diào)用這個(gè)代理函數(shù),就能在其中的某個(gè)部分中添加邏輯。

    before(ted, ted.design, 'painting some blueprints');

上面的全局函數(shù),還可以直接添加進(jìn)對(duì)象中,防止被不必要的類或者方法使用。


    Person.prototype.before = function (fn, ...args) {
        console.log('do sthing before');
        return fn.call(this, args);
    };

    ted.before(ted.design, 'painting some blueprints');

雖然這種形式大體上能解決我們的需求,但是這種寫法,仍然破壞了對(duì)象原有的調(diào)用形式。


    // 原來的調(diào)用形式
    ted.greeting();
    ted.playingGames('DotA2', 'PC');
    ted.design('painting some blueprints');

    // 代理函數(shù)的調(diào)用形式
    ted.before(ted.greeting);
    ted.before(ted.playingGames, 'DotA2', 'PC');
    ted.before(ted.design, 'painting some blueprints');

清一色的 before 函數(shù),不僅寫起來頭暈,而且當(dāng)你要改動(dòng)的時(shí)候,所有調(diào)用這個(gè)代理函數(shù)的地方都要進(jìn)行改動(dòng)。這并不是我們想要的。

非侵入式切面

代理函數(shù)幫我們執(zhí)行了函數(shù),雖然能按照我們的期望執(zhí)行程序,但我們更希望它直接返回函數(shù)給我們,這樣我們就無須費(fèi)勁心思地去到處修改舊代碼,同時(shí)往代碼中添加了新的功能。


    function before(originTarget, fn) {
        return function (...args) {
            console.log('do sthing before');
            return fn.apply(originTarget, args);
        };
    }
    ted.playGames = before(ted, ted.playGames);

    // 正常調(diào)用
    ted.playGames('DotA2', 'PC');

以上代碼還可以寫成更加通用的寫法


    function before (clazz, fn) {
        return function (...args) {
            console.log(`now time: ${new Date()}`);
            return clazz.prototype[fn].apply(this, args);
        }
    }
    ted.greeting = before(Person, 'greeting');

    // 正常調(diào)用
    ted.greeting();

程序中那些無法通過父元素所聚合的共同屬性,但是又切實(shí)地影響到了程序的寫作——這種時(shí)候,切面編程就能發(fā)揮出它的用處。它將業(yè)務(wù)中的相同的部分抽象出來,組成一個(gè)個(gè)可拆卸的業(yè)務(wù)組件。面向?qū)ο笸ㄟ^繼承和多態(tài)的縱軸讓代碼松耦合,而面向切面則是在業(yè)務(wù)并行展開的水平線上讓代碼松耦合。

中間件

提起AOP,總免不了讓人想到Java Spring框架中的監(jiān)聽器、攔截器、過濾器。它們是典型的AOP編程思想的結(jié)晶——一條正常的程序流,被幾個(gè)中間件攔截檢查。

這啟發(fā)了我們,在程序設(shè)計(jì)上的一條新思路:程序暴露出一段執(zhí)行流,并且給開發(fā)者一把剪刀。它們可以隨意在允許的范圍內(nèi)剪開程序,并織入想要執(zhí)行的內(nèi)容。最后這一段執(zhí)行流合重新合并起來,收入程序的深處。

簡(jiǎn)單模擬一個(gè)中間件設(shè)計(jì)


    // 切入點(diǎn)函數(shù)隊(duì)列
    let fns = [];
    // 切點(diǎn)計(jì)數(shù)
    let fnCounter = 0;

    // 啟動(dòng)函數(shù)
    function main() {
        next();
    }

    // 執(zhí)行下一個(gè)函數(shù)
    function next() {
        let fn = fns[fnCounter++];  // 取出函數(shù)數(shù)組里的下一個(gè)函數(shù)
        if (!fn) {    // 如果函數(shù)不存在,return
            return;
        }
        fn(next);   // 否則,執(zhí)行下一個(gè)函數(shù)
    }

    // 將自定義的函數(shù)推入函數(shù)隊(duì)列
    function processer(fn) {
        fns.push(fn);
    }

    function loginCheck(next) {
        if (...)
            next();
        else
            return;
    }

    function characterFilter(next) {
        // doing some character filtering
        next();
    }

    function mainService() {
        // 主業(yè)務(wù)
        console.log('some main services');
    }

    processer(characterFilter);
    processer(loginCheck);
    processer(mainService);
    main(); // 模擬程序啟動(dòng)


原文地址: https://code.evink.me/2018/07/post/Aspect-Oriented-Programming/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本章內(nèi)容: 面向切面編程的基本原理 通過POJO創(chuàng)建切面 使用@AspectJ注解 為AspectJ切面注入依賴 ...
    謝隨安閱讀 3,391評(píng)論 0 9
  • AOP知識(shí)整理 AOP(Aspect-Oriented Programming):面向切面的編程。OOP(Obje...
    wblearn閱讀 3,167評(píng)論 0 11
  • AOP(Aspect-Oriented Programming):面向切面的編程。OOP(Object-Orien...
    badcyc閱讀 340評(píng)論 0 1
  • 在我六歲的那一年,我要開始漫長(zhǎng)的求學(xué)之路,我猜想應(yīng)該有很多的孩子和我當(dāng)年的經(jīng)歷一樣,因?yàn)闆]有提前做好準(zhǔn)備,結(jié)果到了...
    善行無痕閱讀 242評(píng)論 0 0
  • 第五章,我哄你 所以,那個(gè)男孩,在最后離開時(shí)告訴了她他的名字柯景蘇 而她也很快要離開, 白叔叔去尋醫(yī)了,她等他...
    三炮英雄閱讀 326評(píng)論 0 0

友情鏈接更多精彩內(nèi)容