ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

B
Bill Karwin

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

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

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

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

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

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


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

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

他们的结构也是如此。

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

Adapter 和 Facade 的接口都与它们封装的接口不同。但是适配器派生自现有接口,而外观创建一个新接口。

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


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

我对这个问题的看法。

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

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

适配器

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

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

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

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

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

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

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

装饰器

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

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

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

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

代理人

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

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

远程代理 - 主题位于远程服务器、不同的 JVM 甚至非 Java 系统上。代理将方法调用转换为 RMI/REST/SOAP 调用或任何需要的调用,保护客户端免受底层技术的影响。

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

访问代理——控制对主题的访问。

正面

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

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

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

构造函数的区别

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

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

装饰器和适配器确实包装了已经存在的对象,并且通常在构造函数中提供。

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

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


D
Dinah

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

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

我强烈推荐它!


R
Ravindra babu

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

我将装饰关键点。

装饰师:

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

例如(带有链接):java.ioInputStream & 相关的包类OutputStream 个接口

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

代理人:

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

例如:java.rmi 包类。

适配器:

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

例如 java.io.InputStreamReaderInputStream 返回 Reader

桥:

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

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

关键说明:

适配器为其主题提供了不同的接口。代理提供相同的接口。装饰器提供了一个增强的接口。 Adapter 改变了对象的接口,Decorator 增强了对象的职责。 Decorator 和 Proxy 目的不同,但结构相似Bridge 让它们先于它们工作。 Bridge 是预先设计的,让抽象和实现独立变化。适配器被改造以使不相关的类一起工作装饰器旨在让您无需子类化就可以向对象添加职责。

查看有关各种设计模式示例的出色 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


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

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

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

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


T
Teoman shipahi

这是来自 Head First Design Patterns 的引述

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

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

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

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

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


A
Alexey

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

代理将访问封装在外部到内部。

装饰器修改或扩展内部与外部的行为。

适配器将接口从内部转换为外部。

Bridge 将行为的不变部分(外部)与可变或平台相关部分(内部)分开。

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

在 Proxy 接口中是相同的。

在装饰器接口中是相同的。

适配器接口在形式上有所不同,但实现相同的目的。

桥接口在概念上是不同的。


h
hmcclungiii

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


那不就是适配器模式吗?
代理使用 Web 服务,而适配器模式更多地用于将数据从一种形式转换或转换为另一种形式。
b
bnguyen82

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

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

另一件事是,代理与目标完全一样,而其他模式为目标添加了更多功能。


C
Community

我想为 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 { ... }

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

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

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

Bridge 与 Adapter 非常相似,但是当您同时定义抽象接口和底层实现时,我们将其称为 Bridge。即,您不适应某些遗留或第三方代码,您是所有代码的设计者,但您需要能够交换不同的实现。外观是一个或多个类的子系统的更高级别(阅读:更简单)的接口。假设您有一个复杂的概念,需要多个对象来表示。对那组对象进行更改是令人困惑的,因为您并不总是知道哪个对象具有您需要调用的方法。是时候编写一个外观,它为您可以对对象集合执行的所有复杂操作提供高级方法。示例:学校部门的领域模型,具有 countStudents()、reportAttendance()、assignSubstituteTeacher() 等方法。

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


T
Tunaki

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

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

            }
        }
    }

}

M
Mahantesh

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

因此,更多地关注可靠的设计原则、干净的编码原则和 ttd


同意,尽管它没有回答问题。