我理解的裝飾模式

裝飾模式是一個比較抽象的概念。即便在代碼中運用了該模式,或許也不太容易意識到。javascript

它的概念很簡單,在不影響模塊現有功能的前提下,爲模塊增添新的功能。這裏的模塊能夠是方法、函數、類,甚至是系統等等。java

JavaScript

類的裝飾

JS 類支持使用裝飾器,裝飾器是一個函數。它接受一個 target 參數,持有待裝飾類的引用。es6

function runDecorator(target) {
    target.prototype.run = function() {
        console.log(this, 'run');
    };
}

@runDecorator
class Cat {
}

const cat = new Cat();
cat.run(); // 輸出:"run"
複製代碼

上述代碼實現爲 Cat 類添加 run() 方法。但這麼使用裝飾器很雞肋,咱們徹底能夠直接在類中定義這個方法。編程

類裝飾器更多的是用來, hook 類已經存在的方法,並添加新的功能,以便該新功能可以複用。swift

好比有一個需求,如何以日誌的形式監控,類的方法被調用。粗暴的作法是,手動修改類的每個方法,爲它們加上監控代碼。這顯然不科學。app

用裝飾器能夠輕鬆優雅的實現該需求。模塊化

function log(target) {
    const p = target.prototype;
    for (const key in p) {
        if (!p.hasOwnProperty(key)) break;
        const func = p[key];
 	// 過濾出 p 中的方法
        if (typeof func  === 'function') {
            // 建立新方法
            const newFunc = function() {
		// 輸出日誌
                console.log(key.toString());
		// 調用原方法
                func.apply(this, arguments); 
            }
            // 更新方法
            target.prototype[key] = newFunc;
        }
    };
}

@log
class Test { }
複製代碼

具體過程註釋已經比較清晰了。在不改變原有模塊(方法)的前提下,爲模塊「新增」功能,這裏的新增,並無影響原來方法的功能。函數

更多關於 JS 裝飾器的使用,請參考 ES6 Decorator學習

OC Method Swizzling

Objective-C Method Swizzling,即方法交換,是基於 runtime 實現的,它在非侵入的前提下,給方法添加新功能,很好地體現了裝飾模式的思想。ui

// ...
Class class = [self class];

SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);

Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

BOOL didAddMethod =
    class_addMethod(class,
        originalSelector,
        method_getImplementation(swizzledMethod),
        method_getTypeEncoding(swizzledMethod));

if (didAddMethod) {
    class_replaceMethod(class,
        swizzledSelector,
        method_getImplementation(originalMethod),
        method_getTypeEncoding(originalMethod));
} else {
    method_exchangeImplementations(originalMethod, swizzledMethod);
}

// ...

// 使用
- (void)xxx_viewWillAppear:(BOOL)animated {
    [self xxx_viewWillAppear:animated];
    NSLog(@"viewWillAppear: %@", self);
}

複製代碼

示例代碼來源:Method Swizzling

假如一個類的同一個方法在多個 Category 中被交換,那麼這個方法最終的行爲會是怎麼樣的呢?

答案是全部的交換方法都會生效,這也是咱們預期的效果。可是它存在一個很大的缺陷,若是有兩個交換方法的名字不慎重名,那麼這兩個交換方法都不會被調用,更糟糕的是由於是在 runtime 交換的,因此編譯器也不會有任何提示。

此外,類的加載與它在 Build Phases 中的位置有關,咱們固然不能依賴這個位置順序來手動決定類的加載順序。因此若是有屢次方法交換,它們的調用順序是不肯定的。

而 JS 的 Decorator 調用順序是能夠指定的。

Swift 協議和繼承

Protocol 是 Swift 語言的一大特性,不論是 class, struct 仍是 enum 都能使用協議進行擴展。加上 Protocol 不只僅只是添加聲明,還能「自帶」 實現,更是讓 Swift 如虎添翼。

在形式上,協議和繼承很是的像,並且它們都是裝飾模式的應用。可是從裝飾模式的角度,它們存在很多區別。暫且拋開其它的區別,相對於協議,繼承是一種低效的裝飾。

雖然繼承可以複用父類的代碼,可是添加在子類中的新功能想要複用,只可以再繼承,該功能被限制在了特定的類型中。若是要在其它類中使用,只能再寫一遍。

若是沒有特殊的約束,協議是能夠被任何類型遵循的,也就提升了協議中自帶實現的複用率。

繼承和協議的使用形如這樣:

class A: Father, Protocol {
}
複製代碼

A 繼承自 Father,並遵循了 Protocol 協議。這裏 A 繼承 Father 並添加本身的實現,因此它是 Father 的裝飾,而 A 遵循 Protocol,是 Protocol 對它進行裝飾,兩種實現的裝飾主體徹底不一樣。

若是將協議從遵循的類中移除,該類只是缺失協議提供的功能,類自己仍是能夠正常工做。但若是是繼承,修改父類的代碼,子類將會受到影響,然而子類做爲「裝飾」,行爲會被它所裝飾的主題對象的改變,這顯然不科學,是一種假裝飾。

相對來講,協議是低耦合的,也更能體現裝飾模式的核心思想。

總結

從更高的維度講,裝飾模式是 AOP 編程範式的運用。以一種非浸入,模塊化的方式爲已有的代碼添加新特性。我的認爲它的本質做用是便於代碼的維護,插件式的可插拔。

然而選擇必定會存在成本,裝飾模式將功能分散到不一樣的地方,可能會存在功能上的重複和衝突甚至是相互抵消,增長複雜性,也增長了學習和理解的難度。

相關文章
相關標籤/搜索