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

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

86218 次浏览

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

它们非常相似,而且它们之间的线条是灰色的。我建议你阅读c2 wiki中的代理模式装饰器模式条目。

那里的条目和讨论相当广泛,也链接到其他相关文章。顺便说一下,c2 wiki在了解不同模式之间的细微差别时非常有用。

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

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

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

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

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

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

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

在许多GoF模式中有大量的重叠。它们都建立在多态性的力量之上,有时只是意图不同。(战略vs.国家)

在阅读头部优先的设计模式之后,我对模式的理解增加了100倍。

我强烈推荐!

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

它们的结构也是如此。

  • ProxyDecorator都具有与其包装类型相同的接口,但代理在底层创建实例,而Decorator在构造函数中接受实例。

  • 适配器Facade都有不同于它们包装的接口。但是适配器来源于现有的接口,而facade创建了一个新的接口。

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

说到细节实现,我发现代理和Decorator,适配器,Facade之间的区别…在这些模式的常见实现中,有一个被封闭对象包裹的目标对象。客户端使用外围对象而不是目标对象。目标对象实际上在一些封闭对象的方法中扮演着重要的角色。

而对于Proxy,外围对象可以自己扮演一些方法,它只是在客户端调用一些需要目标对象参与的方法时对目标对象进行初始化。这是惰性初始化。对于其他模式,封闭对象实际上是基于目标对象的。目标对象总是和构造函数/setter中的封闭对象一起初始化。

另一件事,代理所做的正是目标所做的,而其他模式则为目标添加了更多功能。

所有这四种模式都涉及到用外部对象/类包装内部对象/类,因此它们在结构上非常相似。我将通过目的来概述不同之处:

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

通过内外物体之间的界面变化:

  • 代理接口中是相同的。
  • 装饰接口中是相同的。
  • 适配器中的接口形式不同,但实现相同的目的。
  • 中的接口在概念上是不同的。

我对这个问题的看法。

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

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

适配器

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

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

适配器保护一个团队不受来自其他团队的易变代码的影响;在处理离岸团队时的救星工具;-)

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

适配器有助于绕过Java只能单一继承的限制。它可以在一个信封下组合多个改编,给人一种多重遗传的印象。

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

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

装饰

Decorator不仅仅是委托,不仅仅是将一个方法映射到另一个,它们做的更多,它们修改一些subject方法的行为,它可以决定不调用subject方法,委托给一个不同的对象,一个helper对象。

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

Decorator必须是subject接口的子类。它们可以透明地使用,而不是其主题。参见BufferedOutputStream,它仍然是OutputStream,可以这样使用。这是与适配器的主要技术区别。

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

代理

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

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

  • 远程代理主题在远程服务器上,不同的JVM甚至非 Java系统。代理将方法调用转换为RMI/REST/SOAP调用或 无论需要什么,保护病人不受潜在危险的影响 李技术。< / p > < / >

  • Lazy Load Proxy -完全初始化对象只有第一次使用或 第一次密集使用。

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

外观

立面与设计的最小知识原则(得墨忒耳定律)密切相关。 Facade非常类似于Adapter。它们都是包装,都是将一个对象映射到另一个对象,但它们的意图不同。立面扁平化了复杂结构的主题,复杂的对象图形,简化了对复杂结构的访问。< / p >

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

适配器模式的更复杂变体,不仅实现不同,而且抽象也不同。它为委托增加了另一种间接方式。额外的代表团是一座桥梁。它甚至从适配接口中分离了适配器。它比其他任何包装模式都增加了更多的复杂性,因此应用时要小心。

构造函数的差异

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

  • Proxy未包装现有对象。构造函数中没有主语。

  • 装饰适配器确实包装已经存在的对象,这通常是

  • 外观构造函数取整个对象图的根元素,否则看起来

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

设计模式不是数学,它是艺术和软件工程的结合。对于这个要求,你必须使用代理,网桥等。创建设计模式是为了解决问题。如果你预见到一个设计问题,那么就使用它。根据经验,您将知道对于特定的问题,使用哪种模式。如果您精通坚实的设计原则,那么您可能已经在不知道它是模式的情况下实现了设计模式。常见的例子是策略和工厂模式

因此,更多地集中在坚实的设计原则,干净的编码原则和ttd

这是引用 头部优先的设计模式 < / p >

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

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

适配器 -转换一个接口到另一个接口。你有一个汽车界面,你想让它像吉普车一样工作。所以你把车开过来,改装成吉普车。因为这不是真正的吉普车。但它就像一辆吉普车。

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

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

我想为Bill Karwing的答案添加一些例子(顺便说一句,这很好。) 我还添加了一些关键的执行差异,我觉得是缺失

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

代理、装饰器、适配器和桥接器都是“包装”类的变体。 但是它们的用途是不同的
  • 代理可以在你想延迟实例化一个对象时使用,或者 隐藏您正在调用远程服务或控制访问的事实

ProxyClass和被代理的ObjectClass应该实现相同的接口,因此它们是可交换的

示例- proxy昂贵对象

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 { ... }
  • 适配器用于当你有一个抽象接口,并且你想 将该接口映射到具有类似功能的另一个对象
  • . .

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

Adapter为其主题提供了不同的接口。Proxy提供相同的接口。Decorator提供了增强的接口。

  • 非常类似于适配器,但当您使用 定义抽象接口和底层实现。 也就是说,你不是在适应一些遗留代码或第三方代码,而是 设计师的所有代码,但你需要能够交换出来 李不同的实现。< / p > < / > 的子系统的一个高级(更简单)接口 一个或多个类。假设您有一个复杂的概念,需要 要表示的多个对象。对该对象集进行更改 是令人困惑的,因为你不知道哪个对象有 方法。这是编写Facade的时候了 为您可以执行的所有复杂操作提供高级方法 到对象的集合。例如:某学校的域模型 section,使用countStudents()reportAttendance()assignSubstituteTeacher(),等等。

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

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

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


}
}
}


}

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

我将装修重点。

< em >修饰符:< / em >

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

例:(带链接):与__ABC1 &相关的__ABC0包类;OutputStream接口

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

代理:< em > < / em >

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

例如:java.rmi包类。

< em >适配器:< / em >

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

例如:java.io.InputStreamReader (InputStream返回Reader)

桥< em >: < / em >

  1. 它允许抽象和实现独立变化
  2. 它使用复合优于继承

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

关键笔记:

  1. < < em >适配器/ em >为它的主题提供了不同的接口。< em > < / em >代理提供了相同的接口。< em >装饰< / em >提供了一个增强的接口。
  2. < < em >适配器/ em >改变对象的接口,< em >装饰< / em >增强对象的职责。
  3. < em >装饰< / em >< em > < / em >代理的用途不同,但结构相似
  4. < < em >适配器/ em >使事情工作后,他们被设计;桥< em > < / em >使他们工作之前。
  5. 桥< em > < / em >是预先设计的,让抽象和实现独立变化。< < em >适配器/ em >经过改造,使不相关的类一起工作
  6. < em >装饰< / em >的设计目的是让你无需子类化就可以向对象添加职责。

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

何时使用装饰器模式?< / >

你什么时候使用桥接模式?它与适配器模式有何不同?< / >

Proxy和Decorator Pattern的差异 .