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


403

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


4
通常,有些模式看起来非常相似,但意图不同(想到的策略和状态模式)。我认为这通常是由于设计模式基于常见的实体设计原则而引起的。
杰森·唐

5
好吧,这四个模式具有完全相同的实现细节。状态经文策略至少可以概括为无状态的全状态经文(大部分情况下)。通常,策略只是方法注入,状态模式使用接口执行更多操作,然后抽象出方法调用。最终,该策略也是一种黑客,可以在OO世界中进行功能编程。
查尔斯·格雷厄姆

Answers:


648

代理,装饰器,适配器和桥都是“包装”类的变体。但是它们的用途不同。

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

  • 装饰器也称为“智能代理”。当您要向对象添加功能而不是通过扩展该对象的类型时,可以使用此方法。这使您可以在运行时这样做。

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

  • Bridge与Adapter非常相似,但是当您定义抽象接口和基础实现时,我们将其称为Bridge。也就是说,您不适应某些遗留代码或第三方代码,而是所有代码的设计者,但您需要能够交换出不同的实现。

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


7
好答案。也许值得添加一些在野外看到它的例子?例如Web服务中的代理类。向我+1。
罗伯·库珀,

5
@Rob:谢谢,但是我希望这个答案简短而有趣。我鼓励您用野外的例子再写一个答案!
Bill Karwin

8
@RobertDailey Decorator也可以避免失控类型层次结构。例如,假设您在GUI中有一个窗口,并且想要有可选的滚动条。您可以具有Window,VScrollWindow,HScrollWindow和VHScrollWindow类,也可以在Window上创建VScroll和HScroll装饰器。
伊娃(Eva)

1
@RobertDailey,装饰组成。
Bill Karwin 2013年

1
如果要以1:1的方式复制包装对象的接口,然后添加一些其他方法,该怎么办?这是装饰器还是适配器?
donquixote 2014年

198

正如Bill的回答所说,它们的用例是不同的

它们的结构也是如此。

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

  • AdapterFacade都具有与其包装不同的接口。但是适配器是从现有接口派生的,而外观则创建了新接口。

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


30
您的答案与Bill的总结相结合,很好地总结了《设计模式》的5章。人们可以称它们为本书的更高层次(阅读:更简单)的界面。
乔纳斯·艾希

54

我对这个问题的看法。

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

通过促进松散耦合,它们使曾经稳定的代码减少了不可避免的更改,并让其他开发人员更易读。

适配器

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

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

适配器使一个团队免受其他团队的易失性代码的攻击;与海上团队打交道时的救生工具;-)

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

适配器有助于绕开Java单一继承的限制。它可以将多个适配器集成到一个信封中,给人留下多重继承的印象。

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

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

装饰器

装饰器不仅委托,而且不仅将一个方法映射到另一方法,而且执行更多操作,修改某些主题方法的行为,它可以决定根本不调用主题方法,委托给另一个对象(帮助对象)。

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

装饰器必须是主题界面的子类。它们可以透明地代替其主题使用。请参见BufferedOutputStream,它仍然是OutputStream,可以这样使用。这是与适配器的主要技术差异。

JDK-Java IO中提供了整个装饰器系列的教科书示例。像BufferedOutputStreamFilterOutputStreamObjectOutputStream之类的所有类都是OutputStream的装饰器。它们可以是洋葱分层的,可以再装饰一个装饰器,从而增加了更多功能。

代理

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

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

  • 远程代理–主题位于远程服务器,其他JVM甚至非Java系统上。代理将方法调用转换为RMI / REST / SOAP调用或所需的任何内容,从而使客户端免于暴露于基础技术。

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

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

正面

外立面与最小知识的设计原理(得墨meter耳法则)密切相关。外观与适配器非常相似。它们都包装,它们都将一个对象映射到另一个对象,但是它们的意图不同。外立面展平主题,复杂对象图的复杂结构,从而简化了对复杂结构的访问。

外墙包裹了一个复杂的结构,为其提供了一个扁平的界面。这样可以防止客户对象暴露于主体结构中的内部关系,从而促进松散耦合。

适配器模式的更复杂的变体,不仅实现有所不同,而且抽象也不同。它为委托添加了另一种间接方式。额外的授权是桥梁。它将适配器与适配接口解耦。与其他包装方式相比,它增加了更多的复杂性,因此请谨慎使用。

构造函数的差异

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

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

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

  • Facade构造函数采用整个对象图的根元素,否则它看起来与Adapter相同。

现实生活中的示例– JAXB编组适配器。此适配器的目的是将简单的平面类映射到外部所需的更复杂的结构,并防止使用过多的注释“污染”主题类。


30

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

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

我强烈推荐它!


9

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

我将装饰关键点。

装饰器:

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

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

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

代理:

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

例如:java.rmi包类。

适配器:

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

例如java.io.InputStreamReaderInputStream返回a Reader

桥:

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

例如中的Collection类java.utilList由实施ArrayList

重要说明:

  1. 适配器为其主题提供了不同的接口。代理提供相同的接口。装饰器提供增强的界面。
  2. 适配器更改对象的界面,装饰器增强对象的职责。
  3. 装饰器代理具有不同的用途,但结构相似
  4. 适配器使事情在设计之后就可以正常工作。让他们先于工作。
  5. Bridge是预先设计的,以使抽象和实现独立变化。适配器经过改造,可以使无关的类一起工作
  6. 装饰器旨在使您无需添加子类即可向对象添加职责。

查看有关各种设计模式示例的出色SE问题/文章

何时使用装饰图案?

您何时使用桥接模式?它与适配器模式有何不同?

代理和装饰器模式之间的差异


8

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

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

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


4

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

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

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

  • 代理服务器的接口是相同的。
  • Decorator接口中是相同的。
  • 适配器接口都不同形式上的,但实现同样的目的。
  • Bridge接口中的概念在概念上是不同的。

4

引自 Head First设计模式

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

装饰器 -不更改界面,但增加责任。假设您有汽车接口,那么当您针对汽车的不同模型(s,sv,sl)实现此接口时,您可能需要为某些模型增加更多责任。如有天窗,安全气囊等。

适配器 -将一个接口转换为另一个接口。您有汽车接口,并且希望它像吉普车一样工作。因此,您乘汽车,对其进行改装并变成一辆吉普车。由于它不是真正的吉普车。但是就像吉普车。

外观 -使界面更简单。假设您有汽车,飞机,轮船界面。实际上,您所需要的只是一门课程,该课程将人们从一个位置转移到另一个位置。您希望立面决定使用哪种车辆。然后,在1个保护伞下收集所有这些接口引用,并让其决定/委托以使其简单。

首先,“ Facade不仅简化了接口,而且使客户端与组件子系统分离。Facade和适配器可以包装多个类,但是Facade的目的是简化,而Adapter的目的是将接口转换为不同的东西。 ”


1

在使用Web服务时,我经常使用它。Proxy Pattern可能应该重命名为更实用的名称,例如“ Wrapper Pattern”。我还有一个库,它是MS Excel的代理。它使Excel的自动化变得非常容易,而不必担心背景细节(例如什么)。版本已安装(如果有)。


那不就是适配器模式吗?
查尔斯·格雷厄姆,

1
Web服务由代理使用,而适配器模式更多地用于将数据从一种形式转换或转换为另一种形式。
hmcclungiii

1

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

但是,在使用代理服务器的情况下,封闭对象可以自己播放一些方法,它只是在客户端调用一些需要目标对象参与的方法时才初始化目标对象。这是延迟的初始化。在其他模式的情况下,包围对象实际上基于目标对象。因此,目标对象总是与构造器/设置器中的封闭对象一起初始化。

另一件事,代理完全可以执行目标的操作,而其他模式则可以为目标添加更多功能。


1

我想在Bill Karwing的答案中添加示例(顺便说一句)。我还添加了一些实现上的关键区别,我觉得这是缺少的

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

代理,装饰器,适配器和桥都是“包装”类的变体。但是它们的用途不同。

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

代理的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的扩展接口。因此,可以用DecoratorClass代替ObjectClass,反之亦然。

示例-添加附加功能

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 { ... }
  • 当您具有抽象接口,并且想要将该接口映射到另一个具有相似功能角色但接口不同的对象时,将使用适配器

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

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

  • Bridge与Adapter非常相似,但是当您定义抽象接口和基础实现时,我们将其称为Bridge。也就是说,您不适应某些遗留代码或第三方代码,而是所有代码的设计者,但您需要能够交换出不同的实现。

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

此答案中的大多数信息来自https://sourcemaking.com/design_patterns,我建议将其作为设计模式的绝佳资源


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

            }
        }
    }

}

-3

设计模式不是数学,而是艺术与软件工程的结合。对于此要求,没有什么比您必须使用代理,桥接器等更合适的了。创建设计模式来解决问题。如果您预计会有设计问题,请使用它。根据经验,您将了解具体问题以及使用哪种模式。如果您具有扎实的设计原则,那么您可能已经实现了设计模式,却不知道它是模式。常见的例子是静态和工厂模式

因此,应将精力更多地集中在可靠的设计原则,简洁的编码原则和ttd上。


同意,尽管它没有回答问题。
里昂,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.