裝飾模式是一個比較抽象的概念。即便在代碼中運用了該模式,或許也不太容易意識到。javascript
它的概念很簡單,在不影響模塊現有功能的前提下,爲模塊增添新的功能。這裏的模塊能夠是方法、函數、類,甚至是系統等等。java
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。學習
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 調用順序是能夠指定的。
Protocol 是 Swift 語言的一大特性,不論是 class, struct 仍是 enum 都能使用協議進行擴展。加上 Protocol 不只僅只是添加聲明,還能「自帶」 實現,更是讓 Swift 如虎添翼。
在形式上,協議和繼承很是的像,並且它們都是裝飾模式的應用。可是從裝飾模式的角度,它們存在很多區別。暫且拋開其它的區別,相對於協議,繼承是一種低效的裝飾。
雖然繼承可以複用父類的代碼,可是添加在子類中的新功能想要複用,只可以再繼承,該功能被限制在了特定的類型中。若是要在其它類中使用,只能再寫一遍。
若是沒有特殊的約束,協議是能夠被任何類型遵循的,也就提升了協議中自帶實現的複用率。
繼承和協議的使用形如這樣:
class A: Father, Protocol {
}
複製代碼
A 繼承自 Father,並遵循了 Protocol 協議。這裏 A 繼承 Father 並添加本身的實現,因此它是 Father 的裝飾,而 A 遵循 Protocol,是 Protocol 對它進行裝飾,兩種實現的裝飾主體徹底不一樣。
若是將協議從遵循的類中移除,該類只是缺失協議提供的功能,類自己仍是能夠正常工做。但若是是繼承,修改父類的代碼,子類將會受到影響,然而子類做爲「裝飾」,行爲會被它所裝飾的主題對象的改變,這顯然不科學,是一種假裝飾。
相對來講,協議是低耦合的,也更能體現裝飾模式的核心思想。
從更高的維度講,裝飾模式是 AOP 編程範式的運用。以一種非浸入,模塊化的方式爲已有的代碼添加新特性。我的認爲它的本質做用是便於代碼的維護,插件式的可插拔。
然而選擇必定會存在成本,裝飾模式將功能分散到不一樣的地方,可能會存在功能上的重複和衝突甚至是相互抵消,增長複雜性,也增長了學習和理解的難度。