背景
这两种模式解决的问题和场景其实是很不同的,但是看到过几处地方总会拿出来比较。因为他们的实现代码是非常相似的。
// 代理模式的代码结构
public interface IA {
void f();
}
public class A impelements IA {
public void f() {
//...
}
}
public class AProxy impements IA {
private IA a;
public AProxy(IA a) {
this.a = a;
}
public void f() {
// 新添加的代理逻辑
a.f();
// 新添加的代理逻辑
}
}
// 装饰器模式的代码结构
public interface IA {
void f();
}
public class A impelements IA {
public void f() {
//...
}
}
public class ADecorator impements IA {
private IA a;
public ADecorator(IA a) {
this.a = a;
}
public void f() {
// 功能增强代码
a.f();
// 功能增强代码
}
}
这两个模式的简易实现代码,除了名字之外是一模一样的。(摘自极客时间)
但其实这样的代码示例我觉得是具有误导性的。因为装饰器模式的话不可能只有一个ADecorator
what
代理模式:
在不改变原始类(被代理类)代码的情况下,通过引入代理类来给原始类附加功能
装饰器模式:
装饰器类是对功能的增强,通过引入一个与原始类相同父类的子类,在实现方法中扩展功能,我们可以对原始类嵌套多个装饰器类
how
我们要给目前的播放器添加一个缓存功能,播放器本身只有单纯的播放逻辑,为了不将缓存逻辑侵入到播放的业务逻辑里,我们将缓存功能的代码放到代理类中完成。
简易代码示例(Swift)
protocol Playable {
func play()
}
class Player: Playable {
func play() {
// 播放器的播放逻辑
}
}
class PlayerCacheDataProxy: Playable {
let player: Playable
init(player: Playable) {
self.player = player
}
func play() {
player.play()
// 进行缓存逻辑
}
}
// 调用端
let player: Playable = PlayerCacheDataProxy()
player.play()
行为隔离和封装是代理模式的主要体现。对于埋点统计,为了不侵入到业务逻辑代码中我们同样可以使用这种思想。
玩过角色扮演游戏的玩家对我接下来的举例应该会理解很快。我们的角色是一个会射箭🏹的角色。我们用一个Shotable
类定义射箭这个行为
protocol Shotable {
func shot()
}
角色的箭的种类有木箭,还有铁箭,甚至还有手枪,毕竟他们都可以shot
// 木箭
class WoodenArrow: Shotable {
func shot() {
// 省略实现
}
}
// 手枪
class Gun: Shotable {
func shot() {
// 省略实现
}
}
除了基础武器,还有附魔系统,我们可以给武器附魔冰冻属性,毒属性,甚至还有连发增强,所有的武器都可以具有以上的属性增强。我们不可能为所有情况都定义不同的类,因为它们组合的可能性太多了。
这个时候将附魔的属性用作装饰器类试试。
// 冰冻箭
class IceShoot: Shotable {
let shooter: Shotable
init(shooter: Shotable) {
self.shooter = shooter
}
func shot() {
// 加入冰冻属性
shooter.shot()
}
}
// 毒箭
class PoisonousShoot: Shotable {
let shooter: Shotable
init(shooter: Shotable) {
self.shooter = shooter
}
func shot() {
// 加入毒属性
shooter.shot()
}
}
我们接着就可以随意打造我们的武器,在加入冰毒属性后附毒都是可以的。
let arrow = WoodenArrow()
let iceArrow = IceShoot(shooter: arrow)
let iceAndPoisonArrow = PoisonousShoot(shooter: iceArrow)
// 木箭->冰冻->有毒
iceAndPoisonArrow.shot()
why
代理和装饰器模式都用到了面向接口而非实现编程,都用到了组合而非继承的思想。通过上面的例子可以看到他们解决的问题是不一样的。
都体现了封装特性,装饰器模式很好的利用了多态。
都为了增强/扩展功能,但是装饰器模式需要调用者自己去组装,了解自己要去添加哪些具体功能,但是代理模式有时不像我上面举的例子代理类那样具体,是缓存还是统计还是说完全交由外部去做都是有可能的。
比如iOS里的UITableViewDelegate
。 他与传统GoF提到的代理模式有些不同,它的作用更像是单纯的回调,暴露回调供给代理类自由做任何事情。
具体使用哪种模式还要看场景和设计者的目的.