代理模式(Proxy) vs 装饰器模式(Decorator)

背景

这两种模式解决的问题和场景其实是很不同的,但是看到过几处地方总会拿出来比较。因为他们的实现代码是非常相似的。

// 代理模式的代码结构
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提到的代理模式有些不同,它的作用更像是单纯的回调,暴露回调供给代理类自由做任何事情。

具体使用哪种模式还要看场景和设计者的目的.

你可能感兴趣的:(代理模式(Proxy) vs 装饰器模式(Decorator))