【问题标题】:Proxy pattern vs. overriding代理模式与覆盖
【发布时间】:2011-04-27 07:00:11
【问题描述】:

假设有一个接口Subject。

interface Subject { void request(); }

我们有一个 RealSubject 类。假设我们想要增强 RealSubject,我们可以使用包裹 RealSubject 的代理模式:

class Proxy implements Subject { 
   private RealSubject ref;
   void request(){ ... }
}

或者我们可以扩展 RealSubject 并覆盖该方法

class EnhancedSubject extends RealSubject {
   @Override
   void request() { ... }
}

哪种方法更好?我知道 Liskov 原则;假设 EnhancedSubject 满足 Liskov 原则。你还考虑继承吗?

如果没有接口Subject(即RealSubject 没有实现任何接口),似乎“继承和覆盖”是唯一的选择,因为在代理模式中没有要实现的接口。如果没有Subject接口,还可以应用Proxy模式吗?

【问题讨论】:

    标签: java design-patterns dynamic-proxy


    【解决方案1】:

    回答您的第一个问题,“哪种方法更好”?

    我个人更喜欢使用接口和代理(或装饰器)模式来实现这样的东西。 (参见:第 16 条:优先组合而不是继承 Effective Java (2nd Edition)

    如果超类 (RealSubject) 不在您的控制之下,即在同一个包中,并且专门为 Extension 设计和记录,则从版本到版本对其实现的任何更改都可能会破坏您对子类的实现 (@987654323 @)。基本上我要说的是:直接依赖具体的实现会导致代码脆弱。

    针对您的第二个问题,“如果EnhancedSubject 满足 Liskov 原则,您还考虑继承吗?

    如果RealSubjectEnhancedSubject 在您的控制下并以相同的生命周期释放,那么使用继承是安全的,但是直接依赖于具体的实现会导致代码脆弱。

    另一个希望能影响您使用接口实现的观点是 Unit 测试。

    例如,使用您想要应用单元测试的情况,将 RealSubject 的模拟依赖注入到您的 Proxy 实现 Subject 中会容易得多,这样您就可以专门测试Proxy 类,而不必完全测试整个对象层次结构,RealSubjectEnhancedSubject,只是为了确认 EnhancedSubject 的行为符合预期。

    我想可以这么说,如果它都是一个非常简单的 API,并且将来几乎不会改变,那么具体的实现只是更简单。 Keep It Simple Stupid (K.I.S.S.) 是最好的策略之一。

    "如果没有 Subject 接口,你还能应用代理模式吗?" 您可以将RealSubject 注入到另一个类中并在内部使用RealSubject,但如果使用RealSubject 的API 直接依赖于具体类,您别无选择,只能使用继承。

    【讨论】:

    • 您能否澄清一下“直接依赖具体实现会导致代码脆弱”的意思。因为我猜调用super的方法和调用那个类的对象的方法差别不大吧?
    • @ArianHosseinzadeh 考虑直接依赖 ArrayList 而不是 List。然后,您会被绑定到使用 ArrayList,因此即使 Stack 或 LinkedList 在语义上更适合您的需求,也不会选择使用 Stack 或 LinkedList(无需更改代码)。这是代码脆弱的一个例子。如果您查找依赖反转/控制反转 (IOC) 以及 SOLID 面向对象设计,那么网络上有大量信息。
    • @ArianHosseinzadeh 虽然我理解你所说的关于使用 super 调用的实现,但这是关于责任的,如果实现需要使用 super 那么这取决于实现,但是作为接口的客户端,您不需要关心它是如何实现其目标的。无论如何,我希望对“脆弱代码”评论有所帮助:)
    【解决方案2】:

    代理/装饰器的优点是它可以与派生类一起重用。这可以将代理与类的实现分开。 (如果我写了无效的java,你将不得不原谅我......已经有一段时间了)

    在这种情况下,您可能会这样写:

    class LoggingSubjectProxy implements Subject
    {
       private Subject ref;
       void request() 
       { 
          log("Called request");
          ref.request();
       }
    }
    

    现在你可以做

    LoggingSubjectProxy l;
    if(dosimple)
    {
        l.ref = SimpleSubject();
    }
    else
    {
        l.ref = ComplexSubject();
    }
    l.request()
    

    如果这对于您想要的扩展来说太过分了,那么您应该使用简单的继承和覆盖。

    【讨论】:

    • ref 是私有的,所以你不能这样做。但是,正如您已经要求的借口,它是完美的。在这里,IMO,让ref 可变不是一个好主意。顺便说一句,它似乎不再是代理了。你说什么?
    • @Adeel Ansari - 同意,我可能会将 ref 作为我的构造函数的一部分。而且这种实现更接近于传统的装饰器模式。代理模式(在我看来)是装饰器模式和桥接模式之间的一部分。
    猜你喜欢
    • 2019-12-18
    • 1970-01-01
    • 2013-12-05
    • 1970-01-01
    • 1970-01-01
    • 2011-11-03
    • 2014-11-09
    • 1970-01-01
    • 2011-07-26
    相关资源
    最近更新 更多