美文网首页Swift
代理模式(Proxy) vs 装饰器模式(Decorator)

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

作者: Zafir_zzf | 来源:发表于2020-05-08 19:23 被阅读0次

    背景

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

    // 代理模式的代码结构
    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)

        本文链接:https://www.haomeiwen.com/subject/nprqnhtx.html