代理,装饰器,适配器和桥接模式如何不同?
我正在寻找代理模式,对我来说,它似乎非常像装饰器,适配器和桥模式。 我误解了什么? 有什么不同? 为什么我会使用代理模式与其他模式? 你过去在现实世界的项目中如何使用它们?
Proxy,Decorator,Adapter和Bridge都是“包装”类的变体。 但他们的用途是不同的。
-
代理可以在你想懒化实例化对象时使用,或者隐藏你调用远程服务的事实,或者控制对象的访问。
-
装饰者也被称为“智能代理”。 当你想添加function到一个对象,而不是通过扩展该对象的types时,这被使用。 这允许你在运行时这样做。
-
当你有一个抽象接口时使用Adapter ,并且你想把这个接口映射到另一个具有相似functionangular色但是有不同接口的对象。
-
Bridge与Adapter非常相似,但在定义抽象接口和底层实现时,我们将其称为Bridge。 即,您不适应一些传统或第三方代码,您是所有代码的devise者,但您需要能够交换不同的实现。
-
门面是一个或多个类的子系统的更高层次(阅读:更简单)的接口。 假设你有一个复杂的概念,需要多个对象来表示。 对这组对象进行更改是令人困惑的,因为您并不总是知道哪个对象具有需要调用的方法。 那是编写一个Facade的时候,它为所有可以对对象集合执行的复杂操作提供了高级方法。 示例:学校部门的领域模型,其中包括
countStudents()
,reportAttendance()
,assignSubstituteTeacher()
等方法。
正如比尔的回答所说,他们的用例是不同的 。
他们的结构也是如此。
-
Proxy和Decorator都和它们的包装types具有相同的接口,但是代理在罩下创build了一个实例,而装饰器在构造函数中接受了一个实例。
-
适配器和Facade都有不同的界面。 但是,适配器从现有的接口派生,而外观创build一个新的接口。
-
桥和适配器都指向一个现有的types。 但是网桥将指向一个抽象types,而适配器可能指向一个具体的types。 桥将允许您在运行时配对实现,而适配器通常不会。
我接受这个问题。
所有四种模式有很多共同之处,所有四种模式有时都被非正式地称为包装或包装模式。 所有使用组合,包装主题和委托执行主题在某个时刻,做一个方法调用映射到另一个。 他们让客户不得不构build不同的对象并复制所有相关数据。 如果明智地使用,它们节省内存和处理器。
通过促进松耦合,一旦稳定的代码不再受到不可避免的变化的影响,并且对于开发人员来说可读性更好。
适配器
适配器使主题(适配器)适应不同的界面。 这样我们可以将对象放置到名义上不同types的集合中。
适配器只向客户端公开相关的方法,可以限制所有其他方法,为特定的上下文显示使用意图,比如调整外部库,使其看起来不那么一般,更关注于我们的应用需求。 适配器增加了我们的代码的可读性和自我描述。
适配器屏蔽了其他团队的不稳定代码, 与离岸团队打交道的人生救世主工具;-)
less提及的目的是防止主题类超过注释。 有了这么多基于注释的框架,这个变得越来越重要了。
适配器有助于避免仅限于单一inheritance的Java限制。 它可以在一个信封下结合几个适应者,给人以多重inheritance的印象。
代码明智的,适配器是“薄”。 除了简单地调用适配器方法和偶然的数据转换来完成这样的调用之外,它不应该为适配器类添加太多的代码。
JDK或基础库中没有很多好的适配器例子。 应用程序开发人员创build适配器,将库调整为专用接口。
装饰
装饰者不仅可以委托,而且可以将一种方法映射到另一种方法,它们做的更多,它们修改某些主题方法的行为,它可以决定不调用主题方法,委托给不同的对象,助手对象。
装饰者通常添加(透明)function包装的对象,如日志logging,encryption,格式或压缩到主题。 这个新的function可能会带来很多新的代码。 因此,修饰器通常比适配器更“肥”。
装饰者必须是主体界面的一个子类。 他们可以透明地使用,而不是其主题。 看到BufferedOutputStream,它仍然是OutputStream,可以这样使用。 这是适配器的主要技术差异。
JDK中的整个装饰器族的教科书示例 – Java IO。 BufferedOutputStream , FilterOutputStream和ObjectOutputStream等类都是OutputStream的装饰器。 他们可以洋葱分层,其中一个装饰器再次装饰,增加更多的function。
代理
代理不是一个典型的包装。 包装的对象,即代理主体,在代理创build时可能还不存在。 代理经常在内部创build它。 它可能是一个按需创build的重对象,或者是在不同的JVM或不同的networking节点中的远程对象,甚至是非Java对象,本地代码中的一个组件。 它不需要包装或委托给另一个对象。
最典型的例子是远程代理,重对象初始化器和访问代理。
-
远程代理 – 主题在远程服务器,不同的JVM甚至非Java系统上。 代理将方法调用转换为RMI / REST / SOAP调用或任何需要的,从而防止客户端接触到底层技术。
-
延迟加载代理 – 仅在第一次使用或第一次使用时完全初始化对象。
-
访问代理 – 控制对主题的访问。
正面
门面与最小知识devise原则(德米特法则)密切相关。 Facade与Adapter很相似。 他们都包装,他们都映射到另一个对象,但他们的意图不同。 外观扁平化了复杂的结构主体,复杂的对象图,简化了对复杂结构的访问。
门面包裹着一个复杂的结构,提供了一个平坦的界面。 这样可以防止客户对象在主体结构中暴露于内部关系,从而促进松耦合。
桥
适配器模式的更复杂的变体,不仅实现变化,而且抽象。 它给代表团增加了一个间接的方向。 额外的授权是桥梁。 它甚至从适配接口中分离适配器。 它比其他任何包装模式更加复杂,所以要小心使用。
构造函数的差异
在查看构造函数时,模式差异也很明显。
-
代理不包装现有的对象。 构造函数中没有主题。
-
装饰者和适配器包装已经存在的对象,这是典型的
在构造函数中提供。 -
Facade构造函数占据整个对象图的根元素,否则它看起来与Adapter相同。
真实的例子 – JAXB编组适配器 。 此适配器的目的是将简单的扁平类映射到外部所需的更复杂的结构,并防止过多的注释“污染”主题类。
在许多GoF模式中有很多重叠。 它们都build立在多态的力量之上,有时候只是真正意图上的不同。 (战略与国家)
阅读Head First Design Patterns之后,我对图案的理解增加了100倍。
我强烈推荐它!
他们非常相似,他们之间的界限很灰暗。 我build议你阅读c2 wiki中的Proxy Pattern和Decorator Pattern条目。
其中的条目和讨论内容相当广泛,也链接到其他相关文章。 顺便说一下,C2的维基是非常好的,当想知道不同的模式之间的细微差别。
总结c2的条目,我会说一个装饰器增加/改变的行为,但代理更多的与访问控制(懒惰实例化,远程访问,安全等)。 但正如我所说,他们之间的界限是灰色的,我看到代理的引用,可以很容易地被视为装饰,反之亦然。
专家的所有好的答案已经解释了每种模式代表什么。
我会装点关键点。
装饰:
- 在运行时将行为添加到对象 。 inheritance是实现这种function的关键,这是这种模式的优点和缺点。
- 它修改接口的行为 。
例如(与链接): 与InputStream
和OutputStream
接口相关的java.io
包类
FileOutputStream fos1 = new FileOutputStream("data1.txt"); ObjectOutputStream out1 = new ObjectOutputStream(fos1);
代理:
- 通过caching对象和控制对客户端/调用者的访问,将其用于延迟初始化,提高性能 。 它可能会提供替代行为或称实物。 在这个过程中,它可能会创build新的对象。
- 与允许链接对象的装饰器不同,代理不允许链接。
例如: java.rmi
包类。
适配器:
- 它允许两个不相关的接口通过不同的对象一起工作 ,可能扮演相同的angular色。
- 它修改原始界面 。
例如java.io.InputStreamReader
( InputStream
返回一个Reader
)
桥:
- 它允许抽象和实现独立地变化 。
- 它使用inheritance的组合 。
例如java.util
Collection类。 由ArrayList
实现的List
。
重要提示:
- 适配器为其主题提供了不同的界面。 代理提供相同的接口。 装饰者提供了一个增强的界面。
- 适配器更改对象的接口, 装饰器增强了对象的职责。
- 装饰者和代理人有不同的目的,但类似的结构
- 适配器在devise完成后就可以使用。 桥在他们之前使他们工作。
- 预先devise网桥 ,使抽象和实现独立变化。 适配器进行改造,使无关的class级一起工作
- 装饰器的目的是让你没有任何子类的责任。
看看有关各种devise模式的例子的很好的SE问题/文章
何时使用装饰模式?
你什么时候使用桥梁模式? 与适配器模式有什么不同?
代理和装饰模式之间的区别
所有这四种模式都涉及用外层包装内部对象/类,所以它们在结构上非常相似。 我会根据目的勾画不同之处:
- 代理封装外部到内部的访问。
- 修饰者修改或扩展内部与外部的行为。
- 适配器将接口从内部转换为外部。
- 桥将行为(外部)的不变部分与variables或平台相关部分(内部)分开。
通过内部和外部对象之间的接口变化:
- 在Proxy界面上是一样的。
- 在Decorator接口中是一样的。
- 在适配器接口是正式的不同,但实现相同的目的。
- 在Bridge接口在概念上是不同的。
在使用Web服务时,我经常使用它。 代理模式可能应该重命名为更实用的东西,比如“Wrapper模式”,我也有一个代理MS Excel的库,它使得Excel自动化非常容易,而不必担心背景细节,比如版本被安装(如果有的话)。
说到详细的实现,我发现代理和装饰,适配器,外观之间的区别…在这些模式的常见实现有一个封闭的对象包装的目标对象。 客户端使用封闭对象而不是目标对象。 而目标对象实际上在封闭对象的一些方法中起着重要的作用。
但是,在Proxy的情况下,封闭对象可以自行播放一些方法,客户端只需要调用目标对象所需的某些方法就可以初始化目标对象,这就是初始化。 在其他模式的情况下,封闭对象实际上是基于目标对象。 所以target对象总是在构造函数/设置器中被封装的对象初始化。
另一件事,一个代理完全做一个目标,而其他模式添加更多的function来定位。
这是Head First Design Patterns的引用
定义属于书。 例子属于我。
装饰者 – 不改变界面,但增加责任。 假设你有一个汽车接口,当你实现这个汽车的不同模型(s,sv,sl)时,你可能需要为某些模型增加更多的责任 。 如有天窗,安全气囊等。
适配器 – 将一个接口转换为另一个接口。 你有一个汽车界面,你想它像吉普车。 所以,你把车,修改它,变成一辆吉普车。 由于它不是真正的吉普车。 但是就像吉普车一样。
Facade – 使界面更简单。 假设你有汽车,飞机,船舶接口。 其实你所需要的只是把人从一个地方送到另一个地方的class级。 你希望立面决定使用什么车辆。 然后你收集所有这些接口引用在1伞下,让它决定/委托保持简单。
首先:“Facade不仅简化了界面,而且还将客户端与组件的子系统分离开来,Facade和适配器可以包装多个类,但Facade的目的是简化,而Adapter则是将接口转换为不同的类。 “
我想补充一些例子给比尔·卡尔威(Bill Karwing)的答案(这是很好的顺便说一句)。我还添加了一些关键的实现差异,我觉得缺less
引用的部分来自[ https://stackoverflow.com/a/350471/1984346%5D(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 { ... }
- 装饰者也被称为“智能代理”。 当你想添加function到一个对象,而不是通过扩展该对象的types时,这被使用。 这允许你在运行时这样做。
DecoratorClass应该(可以)实现ObjectClass的扩展接口。 所以ObjectClass可以被DecoratorClass取代,反之则不然。
示例 – 添加附加function
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 ,并且你想把这个接口映射到另一个具有相似functionangular色但是有不同接口的对象。
实现差异代理,装饰器,适配器
适配器为其主题提供了不同的界面。 代理提供相同的接口。 装饰者提供了一个增强的界面。
Bridge与Adapter非常相似,但在定义抽象接口和底层实现时,我们将其称为Bridge。 即,您不适应一些传统或第三方代码,您是所有代码的devise者,但您需要能够交换不同的实现。
门面是一个或多个类的子系统的更高层次(阅读:更简单)的接口。 假设你有一个复杂的概念,需要多个对象来表示。 对这组对象进行更改是令人困惑的,因为您并不总是知道哪个对象具有需要调用的方法。 那是编写一个Facade的时候,它为所有可以对对象集合执行的复杂操作提供高级方法。 示例:学校部门的领域模型,其中包括
countStudents()
,reportAttendance()
,assignSubstituteTeacher()
等方法。
这个答案中的大部分信息来自https://sourcemaking.com/design_patterns ,我推荐它作为devise模式的优秀资源 。
我相信代码会给出一个清晰的想法(以补充别人的答案)。 请看下面,(聚焦一个类实现的types和包装)
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); } } } } }
devise模式不是math,而是艺术与软件工程的结合。 没有什么比这个要求,你必须使用代理,桥梁等devise模式来解决问题。 如果你预料到一个devise问题,然后使用它。 根据经验,你会知道具体的问题,使用哪种模式。 如果你擅长坚实的devise原则,你就可以在不知道模式的情况下实现devise模式。 常见的例子是statergy和工厂模式
因此更注重坚实的原则,清晰的编码原则和ttd