代理,装饰器,适配器和桥接模式有何不同?

时间:2008-12-08 18:32:07

标签: design-patterns decorator bridge proxy-pattern

我正在查看代理模式,对我而言,它似乎很像装饰器,适配器和桥接模式。我误会了什么吗?有什么不同?为什么我会使用Proxy模式而不是其他模式?你过去在现实世界的项目中如何使用它们?

13 个答案:

答案 0 :(得分:604)

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

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

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

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

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

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

答案 1 :(得分:177)

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

他们的结构也是如此。

  • 代理装饰器都具有与其包装类型相同的界面,但代理会在引擎盖下创建一个实例,而装饰器则会在构造函数。

  • 适配器 Facade 都具有与其包装不同的界面。但适配器派生自现有接口,而外观则创建新接口。

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

答案 2 :(得分:48)

我对这个问题的看法。

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

通过促进松散耦合,他们可以使一旦稳定的代码更少地暴露于不可避免的变化,并为其他开发人员提供更好的可读性。

<强>适配器

适配器使主题(适应者)适应不同的界面。这样我们就可以将对象添加到名义上不同类型的集合中。

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

适配器使一个团队免受其他团队的易变代码的攻击;处理离岸团队时的救生员工具; - )

较少提及的目的是防止主题类过多的注释。有了这么多基于注释的框架,这就变得越来越重要了。

适配器有助于克服仅限单一继承的Java限制。它可以将几个适应者组合在一个信封下,给人以多重继承的印象。

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

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

<强>装饰

Decorator不仅委托,不仅将一个方法映射到另一个方法,它们做得更多,它们修改了一些主题方法的行为,它可以决定不调用主题方法,委托给另一个对象,一个辅助对象。

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

装饰者必须是主题界面的子类。它们可以透明地使用而不是它的主题。请参阅BufferedOutputStream,它仍然是OutputStream,可以这样使用。这是Adapters的主要技术差异。

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

<强>代理

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

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

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

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

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

<强>门面

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

Facade包含复杂的结构,为其提供平面界面。这可以防止客户端对象暴露于主题结构中的内部关系,从而促进松耦合。

<强>桥

适配器模式的更复杂的变体,其中不仅实现变化而且抽象。它为代表团增加了一个间接性。额外的授权是桥梁。它甚至可以从适配接口中解耦适配器。它比任何其他包装模式更增加复杂性,因此请小心使用。

构造函数的差异

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

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

  • 装饰器适配器确实包装了现有对象,通常是 在构造函数中提供。

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

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

答案 3 :(得分:28)

许多GoF模式存在很多重叠。它们都建立在多态性的力量之上,有时只在意图上有所不同。 (战略与国家)

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

我强烈推荐它!

答案 4 :(得分:8)

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

我将装饰关键点。

<强> 装饰:

  1. 在运行时向对象添加行为。继承是实现此功能的关键,这是该模式的优点和缺点。
  2. 它修改了界面的行为
  3. e.g。 (带链接): java.ioInputStream&amp; OutputStream接口

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

    <强> 代理:

    1. 通过缓存对象和控制对客户端/调用者的访问,将其用于延迟初始化,性能改进。它可以提供替代行为或调用真实对象。在此过程中,它可能会创建新的Object。
    2. 与允许链接对象的 Decorator 不同,Proxy 不允许链接。
    3. 例如:java.rmi包类。

      <强> 适配器:

      1. 它允许两个不相关的接口通过不同的对象一起工作,可能扮演相同的角色。
      2. 它修改了原始界面
      3. e.g。 java.io.InputStreamReaderInputStream返回Reader

        <强> 桥:

        1. 它允许抽象和实现独立变化
        2. 它使用组合而不是继承
        3. e.g。 java.util中的集合类。由List实施的ArrayList

          主要提示:

          1. 适配器 为其主题提供了不同的界面。 代理 提供相同的界面。 装饰器 提供增强的界面。
          2. 适配器 更改了对象的界面, 装饰器 增强了对象的职责。
          3. 装饰器 代理商 具有不同的目的,但结构相似
          4. 适配器 可在设计完成后使其正常工作; Bridge 会让它们在工作之前发挥作用。
          5. Bridge 是预先设计的,让抽象和实现独立变化。 适配器 经过改造,使不相关的课程协同工作
          6. 装饰器 旨在让您在不​​进行子类化的情况下向对象添加职责。
          7. 查看有关各种设计模式示例的优秀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

答案 5 :(得分:7)

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

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

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

答案 6 :(得分:3)

这是引自 Head First Design Patterns

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

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

适配器 - 将一个界面转换为另一个界面。你有一个汽车界面,你希望它像吉普车一样。所以你拿车,修改它,变成吉普车。 因为它不是真正的吉普车。但就像吉普车一样。

Facade - 使界面更简单。假设您有汽车,飞机,船舶接口。实际上,你所需要的只是一个将人们从一个地方送到另一个地方的班级。您希望外观决定使用哪种车辆。然后你收集1个伞下的所有接口引用,并让它决定/委托以保持简单。

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

答案 7 :(得分:2)

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

  • 代理封装外部到内部的访问。
  • 装饰器修改或扩展内部与外部的行为。
  • 适配器将界面从内部转换为外部。
  • Bridge 将行为(外部)的不变部分与变量或平台相关部分(内部)分开。

通过内部和外部对象之间的界面变化:

    代理接口中的
  • 相同。
  • 装饰器界面中的
  • 是相同的。
  • 适配器接口正式不同,但实现了相同的目的。 Bridge 界面在概念上有所不同。

答案 8 :(得分:1)

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

答案 9 :(得分:1)

说到详细的实现,我发现Proxy和Decorator,Adapter,Facade之间的区别......在这些模式的通常实现中,有一个被封闭对象包装的目标对象。客户端使用封闭对象而不是目标对象。目标对象实际上在封闭对象的一些方法中起着重要作用。

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

另一方面,代理完全执行目标所做的事情,而其他模式则为目标添加更多功能。

答案 10 :(得分:1)

我想在Bill Karwing的回答中添加一些例子(这很棒。) 我还添加了一些实现的关键差异,我觉得缺少

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

  

代理,装饰器,适配器和桥接器都是&#34;包装&#34;一类。   但它们的用途不同。

     
      当您想要实例化对象时,可以使用
  • 代理,或者   隐藏您正在呼叫远程服务或控制访问的事实   对象。
  •   

代理的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 { ... }
  
      
  • 装饰器也称为&#34;智能代理。&#34;这是在你想要的时候使用的   向对象添加功能,但不能通过扩展该对象来实现   类型。这允许您在运行时执行此操作。
  •   

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 { ... }
  
      如果您有抽象接口,则使用
  • 适配器   将该接口映射到具有类似功能的另一个对象   角色,但不同的界面。
  •   

实现差异代理,装饰器,适配器

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

  
      
  • Bridge 与Adapter非常相似,但我们称之为Bridge   定义抽象接口和底层实现。   即您没有适应某些旧版或第三方代码,您就是其中之一   所有代码的设计者,但你需要能够换掉   不同的实施方式。

  •   
  • Facade 是更高级别(读取:更简单)的子系统接口   一个或多个班级。假设您有一个复杂的概念需要   要表示的多个对象。对该组对象进行更改   令人困惑,因为你并不总是知道哪个对象有   你需要打电话的方法。现在是时候写一个Facade了   为您可以执行的所有复杂操作提供高级方法   到对象的集合。示例:学校的域模型   部分,使用countStudents()reportAttendance()等方法,   assignSubstituteTeacher(),依此类推。

  •   

此答案中的大部分信息来自 https://sourcemaking.com/design_patterns ,我建议将其用作设计模式的优秀资源

答案 11 :(得分:0)

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

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);
                }

            }
        }
    }

}

答案 12 :(得分:-2)

设计模式不是数学,它是艺术与软件工程的结合。没有什么比这个要求你必须使用代理,桥接等。创建设计模式来解决问题。如果您预计到设计问题,请使用它。根据经验,您将了解具体问题,使用哪种模式。如果你擅长坚实的设计原则,你就可以实现设计模式而不需要知道模式。常见的例子是statergy和工厂模式

因此,更多地关注固体设计原则,清洁编码原则和ttd