装饰模式是一个比较抽象的概念。即便在代码中运用了该模式,或许也不太容易意识到。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 编程范式的运用。以一种非浸入,模块化的方式为已有的代码添加新特性。我的认为它的本质做用是便于代码的维护,插件式的可插拔。
然而选择必定会存在成本,装饰模式将功能分散到不一样的地方,可能会存在功能上的重复和冲突甚至是相互抵消,增长复杂性,也增长了学习和理解的难度。