【问题标题】:How do the Proxy, Decorator, Adapter, and Bridge Patterns differ?代理、装饰器、适配器和桥接模式有何不同?
【发布时间】:2010-09-25 21:51:08
【问题描述】:

我正在研究代理模式,在我看来,它看起来很像装饰器、适配器和桥接模式。我是不是误会了什么?有什么不同?为什么我要使用代理模式而不是其他模式?您过去是如何在现实世界的项目中使用它们的?

【问题讨论】:

  • 通常有一些模式看起来非常相似,但它们的意图却不同(想到的策略和状态模式)。我认为这通常是因为设计模式基于常见的可靠设计原则。
  • 嗯,这四种模式的实现细节完全相同。 State verses Strategy 至少可以概括为 state-full verses 无状态(大部分)。通常,策略只是方法注入,其中状态模式使用接口做更多事情,然后抽象出方法调用。归根结底,该策略也是一种允许在 OO 世界中进行函数式编程的技巧。

标签: design-patterns decorator bridge proxy-pattern


【解决方案1】:

Proxy、Decorator、Adapter 和 Bridge 都是“包装”类的变体。但它们的用途不同。

  • 代理可以在您想要延迟实例化对象、隐藏您正在调用远程服务的事实或控制对对象的访问时使用。

  • 装饰器也称为“智能代理”。当您想向对象添加功能但不扩展该对象的类型时使用此选项。这允许您在运行时这样做。

  • 适配器用于当您有一个抽象接口,并且您想将该接口映射到另一个具有类似功能但接口不同的对象时。

    李>
  • Bridge 与 Adapter 非常相似,但是当您同时定义抽象接口和底层实现时,我们将其称为 Bridge。 IE。你没有适应一些遗留或第三方代码,你是所有代码的设计者,但你需要能够交换不同的实现。

  • Facade 是一个或多个类的子系统的更高级别(阅读:更简单)的接口。假设您有一个复杂的概念,需要多个对象来表示。对那组对象进行更改是令人困惑的,因为您并不总是知道哪个对象具有您需要调用的方法。是时候编写一个外观,它为您可以对对象集合执行的所有复杂操作提供高级方法。示例:学校部门的领域模型,具有 countStudents()reportAttendance()assignSubstituteTeacher() 等方法。

【讨论】:

  • 好答案。可能值得添加一些你在野外看到它的例子?例如 Web 服务中的代理类。来自我的 +1。
  • @Rob:谢谢,但我宁愿让这个答案简短而甜蜜。我鼓励你用野外的例子写另一个答案!
  • @RobertDailey 装饰器也可以很好地避免失控的类型层次结构。 For example,假设您在 GUI 中有一个窗口,并且您希望有可选的滚动条。你可以有 Window、VScrollWindow、HScrollWindow 和 VHScrollWindow 类,或者你可以在 Window 上制作 VScroll 和 HScroll 装饰器。
  • @RobertDailey,装饰者作文。
  • 如果你想 1:1 复制被包装对象的接口,然后添加一些额外的方法怎么办?这是装饰器还是适配器?
【解决方案2】:

As Bill's answer says, their use cases are different.

它们的结构也是如此。

  • ProxyDecorator 都具有与其包装类型相同的接口,但代理在后台创建一个实例,而装饰器在构造函数。

  • AdapterFacade 都具有与其封装不同的界面。但是适配器派生自现有接口,而外观创建一个新接口。

  • BridgeAdapter 都指向现有类型。但是桥将指向一个抽象类型,而适配器可能指向一个具体类型。桥接器允许您在运行时配对实现,而适配器通常不会。

【讨论】:

  • 您的回答结合比尔的回答很好地总结了设计模式的 5 章。人们可以将它们称为本书的更高级别(阅读:更简单)的界面。
  • 我会说Adapter 通常依赖于一个接口(需要适应另一个接口 - 就像你所说的派生),但它仍然可能创建(引入)一个新接口(适应从依赖接口)。 AdapterFacade 之间的区别在于依赖的数量,Facade 通常会消耗很多杂项接口(不仅仅是通常像Adapter 这样的接口)并将它们组合起来以吐出一个通用接口,用于某种目的.
【解决方案3】:

我对这个主题的看法。

所有四种模式都有很多共同点,所有四种模式有时都被非正式地称为包装器或包装器模式。所有使用组合,包装主题并在某个时候将执行委托给主题,将一个方法调用映射到另一个方法调用。它们使客户不必构建不同的对象并复制所有相关数据。如果使用得当,它们可以节省内存和处理器。

通过促进松散耦合,它们使曾经稳定的代码更少暴露于不可避免的变化中,并且对于其他开发人员来说更好地可读。

适配器

适配器将主题(适配器)适配到不同的接口。通过这种方式,我们可以将对象添加到名义上不同类型的集合中。

适配器只向客户端公开相关方法,可以限制所有其他方法,揭示特定上下文的使用意图,例如调整外部库,使其看起来不那么通用,更专注于我们的应用程序需求。适配器增加了我们代码的可读性和自我描述。

适配器保护一个团队免受其他团队的易失代码的影响;与离岸团队打交道时的救星工具;-)

较少提及的目的是防止主题类过多的注释。有这么多基于注释的框架,这变得比以往任何时候都更重要。

Adapter 有助于解决 Java 对单一继承的限制。它可以将多个适配器组合在一个信封中,给人以多重继承的印象。

在代码方面,适配器很“瘦”。除了简单地调用适配器方法和进行此类调用所需的偶尔数据转换之外,它不应向适配器类添加太多代码。

JDK 或基本库中没有很多好的适配器示例。应用程序开发人员创建适配器,以使库适应应用程序特定的接口。

装饰器

装饰器不只是委托,不只是将一个方法映射到另一个,他们做的更多,他们修改一些主体方法的行为,它可以决定根本不调用主体方法,委托给不同的对象,一个辅助对象。

装饰器通常会(透明地)向包装对象添加功能,例如记录、加密、格式化或压缩到主题。这个新功能可能会带来很多新代码。因此,装饰器通常比适配器“胖”得多。

装饰器必须是主题接口的子类。它们可以透明地使用而不是其主题。请参阅 BufferedOutputStream,它仍然是 OutputStream,可以这样使用。这是与适配器的主要技术差异。

整个装饰器系列的教科书示例很容易在 JDK - Java IO 中。所有像BufferedOutputStreamFilterOutputStreamObjectOutputStream这样的类都是OutputStream的装饰器。它们可以是洋葱层,其中一个装饰器再次装饰,增加更多功能。

代理

代理不是典型的包装器。被包装的对象,即代理主体,在创建代理时可能还不存在。代理经常在内部创建它。它可能是按需创建的重对象,也可能是不同 JVM 或不同网络节点中的远程对象,甚至是非 Java 对象,本机代码中的组件。它根本不需要包装或委托给另一个对象。

最典型的例子是远程代理、重对象初始化器和访问代理。

  • 远程代理 – 主题位于远程服务器、不同的 JVM 甚至非 爪哇系统。代理将方法调用转换为 RMI/REST/SOAP 调用或 无论需要什么,保护客户免于暴露于底层证券 技术。

  • 延迟加载代理 – 仅在第一次使用时完全初始化对象或 第一次密集使用。

  • 访问代理 - 控制对主题的访问。

立面

Facade 与最少知识的设计原则(得墨忒耳法则)密切相关。 Facade 与 Adapter 非常相似。它们都包裹,它们都将一个对象映射到另一个对象,但它们的意图不同。 Facade 将复杂结构的主体、复杂对象图展平,简化了对复杂结构的访问。

Facade 封装了一个复杂的结构,为它提供了一个平面接口。这可以防止客户端对象暴露于主体结构中的内部关系,从而促进松散耦合。

桥梁

适配器模式的更复杂变体,不仅实现不同,而且抽象也不同。它为委托增加了一种间接性。额外的代表团是桥梁。它甚至将适配器与适配接口解耦。它比其他任何包装模式都增加了复杂性,因此请谨慎应用。

构造函数的区别

查看构造函数时,模式差异也很明显。

  • 代理 未包装现有对象。构造函数中没有主题。

  • DecoratorAdapter 确实包装了已经存在的对象,这通常是
    在构造函数中提供。

  • Facade 构造函数获取整个对象图的根元素,否则看起来 与适配器相同。

现实生活中的例子——JAXB Marshalling Adapter。这个适配器的目的是将一个简单的平面类映射到外部需要的更复杂的结构,并防止过多的注释“污染”主题类。

【讨论】:

    【解决方案4】:

    许多 GoF 模式存在大量重叠。它们都建立在多态性的力量之上,有时只是在意图上真正不同。 (战略与状态)

    阅读Head First Design Patterns后,我对模式的理解增加了100倍。

    强烈推荐!

    【讨论】:

      【解决方案5】:

      专家的所有好的答案都已经解释了每种模式代表什么。

      我会装饰关键点。

      装饰者:

      1. 在运行时向对象添加行为。继承是实现这一功能的关键,这既是这种模式的优点也是缺点。
      2. 它修改了界面的行为

      例如(带有链接):java.ioInputStreamOutputStream 接口相关的包类

      FileOutputStream fos1 = new FileOutputStream("data1.txt");  
      ObjectOutputStream out1 = new ObjectOutputStream(fos1);
      

      代理:

      1. 将它用于延迟初始化,通过缓存对象和控制对客户端/调用者的访问来提高性能。它可以提供替代行为或调用真实对象。在此过程中,它可能会创建新的对象。
      2. 与允许链接对象的装饰器不同,代理不允许链接。

      例如:java.rmi 包类。

      适配器:

      1. 它允许两个不相关的接口通过不同的对象协同工作,可能扮演相同的角色。
      2. 它修改了原来的界面

      例如java.io.InputStreamReaderInputStream 返回一个Reader

      桥:

      1. 它允许抽象和实现独立变化
      2. 它使用组合而不是继承

      例如java.util 中的集合类。 ListArrayList实现。

      重点说明:

      1. Adapter 为其主题提供不同的接口。 代理 提供相同的接口。 装饰器提供了增强的界面。
      2. Adapter 改变对象的接口,Decorator 增强对象的职责。
      3. DecoratorProxy 用途不同,但结构相似
      4. Adapter 让设计完成后的工作; Bridge 让他们提前工作。
      5. Bridge 是预先设计的,让抽象和实现独立变化。 Adapter 进行了改造,使不相关的类可以协同工作
      6. 装饰器旨在让您无需子类化即可向对象添加职责。

      查看关于各种设计模式示例的优秀 SE 问题/文章

      When to Use the Decorator Pattern?

      When do you use the Bridge Pattern? How is it different from Adapter pattern?

      Differences between Proxy and Decorator Pattern

      【讨论】:

      • 抱歉没有明白你的意思。关于你说的装饰器,'继承是实现这个功能的关键,这是这个模式的优点和缺点'。同时,“装饰器旨在让您在没有子类化的情况下向对象添加职责”。这两个在我看来是相互矛盾的
      【解决方案6】:

      它们非常相似,它们之间的线条非常灰色。我建议你阅读 c2 wiki 中的 Proxy PatternDecorator Pattern 条目。

      那里的条目和讨论相当广泛,并且它们还链接到其他相关文章。顺便说一句,c2 wiki 在想知道不同模式之间的细微差别时非常出色。

      总结 c2 条目,我会说装饰器添加/更改行为,但代理更多地与访问控制有关(延迟实例化、远程访问、安全等)。但就像我说的,它们之间的界线是灰色的,我看到对代理的引用很容易被视为装饰器,反之亦然。

      【讨论】:

        【解决方案7】:

        这是引自 Head First Design Patterns

        定义属于书。例子属于我。

        装饰器 - 不改变界面,但增加责任。假设你有一个汽车接口, 当您为不同型号的汽车(s、sv、sl)实施此操作时,您可能需要为某些型号添加更多责任。比如有天窗、安全气囊等。

        适配器 - 将一个接口转换为另一个接口。你有一个汽车接口,你希望它像吉普车一样工作。所以你开着车,改装它,然后变成一辆吉普车。 因为它不是真正的吉普车。但表现得像吉普车。

        外观 - 使界面更简单。假设您有汽车、飞机、船舶接口。实际上,您所需要的只是一门将人们从一个位置发送到另一个位置的课程。您希望外观来决定使用什么车辆。然后,您将所有这些接口引用收集到 1 个保护伞下,并让它决定/委托以保持简单。

        Head First:“外观不仅简化了界面,还将客户端与子系统分离 的组件。 外观和适配器可以包装多个类,但外观的目的是简化,而 适配器的作用是将接口转换为不同的东西。”

        【讨论】:

          【解决方案8】:

          所有四种模式都涉及用外部对象/类包装内部对象/类,因此它们在结构上非常相似。我将按目的概述差异:

          • 代理封装了从外到内的访问。
          • 装饰器修改或扩展内部与外部的行为。
          • Adaptor 将接口从内部转换为外部。
          • Bridge 将行为的不变部分(外部)与可变的或平台相关的部分(内部)分开。

          并且通过内部和外部对象之间的接口变化:

          • 代理接口是一样的。
          • Decorator 接口中是相同的。
          • Adaptor中的接口形式不同,但实现相同的目的。
          • Bridge 接口在概念上有所不同。

          【讨论】:

            【解决方案9】:

            我在使用网络服务时经常使用它。代理模式可能应该重命名为更实用的东西,比如“包装模式”。我还有一个库,它是 MS Excel 的代理。它可以很容易地自动化 Excel,而不必担心背景细节,比如什么版本已安装(如果有)。

            【讨论】:

            • 不就是适配器模式吗?
            • Web 服务由代理使用,而适配器模式更多地用于将数据从一种形式转换或翻译成另一种形式。
            【解决方案10】:

            说到细节实现,我发现代理和装饰器、适配器、外观之间的区别......在这些模式的常见实现中,目标对象被一个封闭对象包裹。客户端使用封闭对象而不是目标对象。而目标对象实际上在一些封闭对象的方法中起着重要的作用。

            但是,在Proxy的情况下,封闭对象可以自己播放一些方法,它只是在客户端调用一些需要目标对象参与的方法时初始化目标对象。这就是惰性初始化。在其他模式的情况下,封闭对象实际上是基于目标对象。所以目标对象总是与构造函数/设置器中的封闭对象一起初始化。

            另一件事,代理完全符合目标的功能,而其他模式为目标添加更多功能。

            【讨论】:

              【解决方案11】:

              我想为 Bill Karwing 的答案添加示例(顺便说一句,这很棒。) 我还添加了一些我觉得缺少的关键实现差异

              引用部分来自 [https://stackoverflow.com/a/350471/1984346] (Bill Karwing) 的回答

              Proxy、Decorator、Adapter 和 Bridge 都是“包装”类的变体。 但它们的用途不同。

              • 代理可以在你想延迟实例化一个对象时使用,或者 隐藏您正在调用远程服务或控制访问的事实 到对象。

              ProxyClass和被代理的ObjectClass,应该实现相同的接口,所以可以互换

              示例 - 代理昂贵的对象

              class ProxyHumanGenome implements GenomeInterface  {
                  private $humanGenome = NULL; 
              
                  // humanGenome class is not instantiated at construct time
                  function __construct() {
                  }
              
                  function getGenomeCount() {
                      if (NULL == $this->humanGenome) {
                          $this->instantiateGenomeClass(); 
                      }
                      return $this->humanGenome->getGenomeCount();
                  }
              } 
              class HumanGenome implement GenomeInterface { ... }
              
              • 装饰器也称为“智能代理”。当您想要 向对象添加功能,但不是通过扩展该对象的 类型。这允许您在运行时这样做。

              DecoratorClass 应该(可以)实现 ObjectClass 的扩展接口。所以 ObjectClass 可以被 DecoratorClass 替换,但反过来不行。

              示例 - 添加附加功能

              class DecoratorHumanGenome implements CheckGenomeInterface  {
              
                  // ... same code as previous example
              
                  // added functionality
                  public function isComplete() {
                      $this->humanGenome->getCount >= 21000
                  }
              }
              
              interface CheckGenomeInterface extends GenomeInterface {
              
                  public function isComplete();
              
              }
              
              class HumanGenome implement GenomeInterface { ... }
              
              • 适配器用于当你有一个抽象接口,并且你想 将该接口映射到另一个具有类似功能的对象 角色,但界面不同。

              实现上的区别Proxy、Decorator、Adapter

              适配器为其主题提供不同的接口。代理提供相同的接口。 Decorator 提供了增强的界面。

              • Bridge 与 Adapter 非常相似,但当您使用时我们称它为 Bridge 定义抽象接口和底层实现。 IE。你没有适应一些遗留或第三方代码,你 所有代码的设计者,但您需要能够换出 不同的实现。

              • Facade 是对 一门或多门课。假设您有一个复杂的概念,需要 多个对象来表示。对该组对象进行更改 令人困惑,因为您并不总是知道哪个对象具有 您需要调用的方法。是时候写一个 Facade 了 为您可以执行的所有复杂操作提供高级方法 到对象的集合。示例:学校的领域模型 部分,使用 countStudents()reportAttendance() 等方法, assignSubstituteTeacher(),等等。

              此答案中的大部分信息来自 https://sourcemaking.com/design_patterns,我推荐它作为设计模式的优秀资源

              【讨论】:

                【解决方案12】:

                我相信代码会给出一个清晰的想法(也可以补充其他答案)。请看下文,(关注类实现和包装的类型)

                using System;
                using System.Collections.Generic;
                using System.Linq;
                using System.Text;
                using System.Threading.Tasks;
                
                namespace TestConsole
                {
                    class Program
                    {
                        static void Main(string[] args)
                        {
                            /* Proxy */
                
                            Console.WriteLine(Environment.NewLine);
                            Console.WriteLine("PROXY");
                            Console.WriteLine(Environment.NewLine);
                
                            //instead of creating here create using a factory method, the facory method will return the proxy
                            IReal realProxy = new RealProxy();
                            Console.WriteLine("calling do work with the proxy object ");
                            realProxy.DoWork();
                
                            Console.WriteLine(Environment.NewLine);
                            Console.WriteLine("ADAPTER");
                            Console.WriteLine(Environment.NewLine);
                
                            /*Adapter*/
                            IInHand objectIHave = new InHand();
                            Api myApi = new Api();
                            //myApi.SomeApi(objectIHave); /*I cant do this, use a adapter then */
                            IActual myAdaptedObject = new ActualAdapterForInHand(objectIHave);
                            Console.WriteLine("calling api with  my adapted obj");
                            myApi.SomeApi(myAdaptedObject);
                
                
                            Console.WriteLine(Environment.NewLine);
                            Console.WriteLine("DECORATOR");
                            Console.WriteLine(Environment.NewLine);
                
                            /*Decorator*/
                            IReady maleReady = new Male();
                            Console.WriteLine("now male is going to get ready himself");
                            maleReady.GetReady();
                
                            Console.WriteLine(Environment.NewLine);
                
                            IReady femaleReady = new Female();
                            Console.WriteLine("now female is going to get ready her self");
                            femaleReady.GetReady();
                
                            Console.WriteLine(Environment.NewLine);
                
                            IReady maleReadyByBeautician = new Beautician(maleReady);
                            Console.WriteLine("now male is going to get ready by beautician");
                            maleReadyByBeautician.GetReady();
                
                            Console.WriteLine(Environment.NewLine);
                
                            IReady femaleReadyByBeautician = new Beautician(femaleReady);
                            Console.WriteLine("now female is going to get ready by beautician");
                            femaleReadyByBeautician.GetReady();
                
                            Console.WriteLine(Environment.NewLine);
                
                            Console.ReadLine();
                
                
                        }
                    }
                
                    /*Proxy*/
                
                    public interface IReal
                    {
                        void DoWork();
                    }
                
                    public class Real : IReal
                    {
                        public void DoWork()
                        {
                            Console.WriteLine("real is doing work ");
                        }
                    }
                
                
                    public class RealProxy : IReal
                    {
                        IReal real = new Real();
                
                        public void DoWork()
                        {
                            real.DoWork();
                        }
                    }
                
                    /*Adapter*/
                
                    public interface IActual
                    {
                        void DoWork();
                    }
                
                    public class Api
                    {
                        public void SomeApi(IActual actual)
                        {
                            actual.DoWork();
                        }
                    }
                
                    public interface IInHand
                    {
                        void DoWorkDifferently();
                    }
                
                    public class InHand : IInHand
                    {
                        public void DoWorkDifferently()
                        {
                            Console.WriteLine("doing work slightly different ");
                        }
                    }
                
                    public class ActualAdapterForInHand : IActual
                    {
                        IInHand hand = null;
                
                        public ActualAdapterForInHand()
                        {
                            hand = new InHand();
                        }
                
                        public ActualAdapterForInHand(IInHand hnd)
                        {
                            hand = hnd;
                        }
                
                        public void DoWork()
                        {
                            hand.DoWorkDifferently();
                        }
                    }
                
                    /*Decorator*/
                
                    public interface IReady
                    {
                        void GetReady();
                    }
                
                    public class Male : IReady
                    {
                        public void GetReady()
                        {
                            Console.WriteLine("Taking bath.. ");
                            Console.WriteLine("Dress up....");
                        }
                    }
                
                    public class Female : IReady
                    {
                        public void GetReady()
                        {
                            Console.WriteLine("Taking bath.. ");
                            Console.WriteLine("Dress up....");
                            Console.WriteLine("Make up....");
                        }
                    }
                
                    //this is a decorator
                    public class Beautician : IReady
                    {
                        IReady ready = null;
                
                        public Beautician(IReady rdy)
                        {
                            ready = rdy;
                        }
                
                        public void GetReady()
                        {
                            ready.GetReady();
                            Console.WriteLine("Style hair ");
                
                            if (ready is Female)
                            {
                                for (int i = 1; i <= 10; i++)
                                {
                                    Console.WriteLine("doing ready process " + i);
                                }
                
                            }
                        }
                    }
                
                }
                

                【讨论】:

                  【解决方案13】:

                  设计模式不是数学,它是艺术和软件工程的结合。对于这种要求,您不必使用代理、桥接等。设计模式是为了解决问题而创建的。如果您预计会出现设计问题,请使用它。根据经验,您将了解具体问题,使用哪种模式。如果你擅长扎实的设计原则,你会在不知道它是模式的情况下实现设计模式。常见的例子是策略和工厂模式

                  因此更多地关注可靠的设计原则、简洁的编码原则和 ttd

                  【讨论】:

                  • 同意,虽然它没有回答问题。
                  猜你喜欢
                  • 2011-01-16
                  • 2012-05-25
                  • 2011-03-30
                  • 2010-09-24
                  • 1970-01-01
                  • 2017-04-21
                  • 2010-11-28
                  • 2013-09-08
                  相关资源
                  最近更新 更多