我在看代理模式,对我来说,它看起来很像装饰器、适配器和桥接模式。我是不是误会了什么?有什么不同?为什么我要使用代理模式而不是其他模式?您过去是如何在现实世界的项目中使用它们的?
13 回答
Proxy、Decorator、Adapter 和 Bridge 都是“包装”类的变体。但它们的用途不同。
当你想延迟实例化一个对象,或者隐藏你正在调用远程服务的事实,或者控制对对象的访问时,可以使用代理。
Decorator也称为“智能代理”。当您想向对象添加功能但不扩展该对象的类型时使用此选项。这允许您在运行时这样做。
当您有一个抽象接口并且您希望将该接口映射到另一个具有相似功能但接口不同的对象时,将使用适配器。
Bridge与 Adapter 非常相似,但是当您同时定义抽象接口和底层实现时,我们将其称为 Bridge。即,您不适应某些遗留或第三方代码,您是所有代码的设计者,但您需要能够交换不同的实现。
外观是一个或多个类的子系统的更高级别(阅读:更简单)的接口。假设您有一个复杂的概念,需要多个对象来表示。对那组对象进行更改是令人困惑的,因为您并不总是知道哪个对象具有您需要调用的方法。是时候编写一个外观,它为您可以对对象集合执行的所有复杂操作提供高级方法。示例:学校部门的领域模型,具有
countStudents()
、reportAttendance()
、assignSubstituteTeacher()
等方法。
他们的结构也是如此。
Proxy和Decorator都具有与其包装类型相同的接口,但代理在后台创建一个实例,而装饰器在构造函数中获取一个实例。
Adapter和Facade的接口都与它们封装的接口不同。但是适配器派生自现有接口,而外观创建一个新接口。
Bridge和Adapter都指向现有类型。但是桥将指向一个抽象类型,而适配器可能指向一个具体类型。桥将允许您在运行时配对实现,而适配器通常不会。
我对这个问题的看法。
所有四种模式都有很多共同点,所有四种模式有时都被非正式地称为包装器或包装器模式。所有人都使用组合,包装主题并在某个时候将执行委托给主题,将一个方法调用映射到另一个方法调用。它们使客户不必构建不同的对象并复制所有相关数据。如果使用得当,它们可以节省内存和处理器。
通过促进松散耦合,它们使曾经稳定的代码更少暴露于不可避免的变化,并且对于其他开发人员来说更好地可读。
适配器
适配器使主题(适配器)适应不同的接口。通过这种方式,我们可以将对象添加到名义上不同类型的集合中。
适配器只向客户端公开相关方法,可以限制所有其他方法,揭示特定上下文的使用意图,例如调整外部库,使其看起来不那么通用,更专注于我们的应用程序需求。适配器增加了我们代码的可读性和自我描述。
适配器保护一个团队免受其他团队的易失性代码的影响;与离岸团队打交道时的救星工具;-)
较少提及的目的是防止主题类过多的注释。有这么多基于注释的框架,这变得比以往任何时候都更重要。
适配器有助于绕过 Java 仅单继承的限制。它可以将多个适配器组合在一个信封下,给人以多重继承的印象。
代码方面,适配器是“瘦”的。除了简单地调用适配器方法和进行此类调用所需的偶尔数据转换之外,它不应向适配器类添加太多代码。
JDK 或基本库中没有很多好的适配器示例。应用程序开发人员创建适配器,以使库适应应用程序特定的接口。
装饰器
装饰器不仅委托,不仅将一个方法映射到另一个,他们做的更多,他们修改一些主体方法的行为,它可以决定根本不调用主体方法,委托给不同的对象,一个辅助对象。
装饰器通常(透明地)向包装对象添加功能,例如记录、加密、格式化或压缩到主题。这个新功能可能会带来很多新代码。因此,装饰器通常比适配器“胖”得多。
装饰器必须是主题接口的子类。它们可以透明地使用而不是其主题。请参阅 BufferedOutputStream,它仍然是 OutputStream,可以这样使用。这是与适配器的主要技术差异。
整个装饰器系列的教科书示例很容易在 JDK - Java IO 中。所有类如BufferedOutputStream、FilterOutputStream和ObjectOutputStream都是OutputStream的装饰器。它们可以是洋葱层,其中一个装饰器再次装饰,增加更多功能。
代理
代理不是典型的包装器。被包装的对象,即代理主体,在创建代理时可能还不存在。代理经常在内部创建它。它可能是按需创建的重对象,也可能是不同JVM或不同网络节点中的远程对象,甚至是非Java对象,本机代码中的组件。它根本不需要包装或委托给另一个对象。
最典型的例子是远程代理、重对象初始化器和访问代理。
远程代理 - 主题位于远程服务器、不同的 JVM 甚至非 Java 系统上。代理将方法调用转换为 RMI/REST/SOAP 调用或任何需要的调用,保护客户端免受底层技术的影响。
延迟加载代理 - 仅在第一次使用或第一次密集使用时完全初始化对象。
访问代理——控制对主题的访问。
正面
立面与最少知识的设计原则(得墨忒耳法则)密切相关。Facade 与 Adapter 非常相似。它们都包裹,它们都将一个对象映射到另一个对象,但它们的意图不同。Facade 将复杂结构的主体、复杂对象图展平,简化了对复杂结构的访问。
Facade 包装了一个复杂的结构,为它提供了一个平面接口。这可以防止客户端对象暴露于主体结构中的内部关系,从而促进松散耦合。
桥
适配器模式的更复杂变体,其中不仅实现不同,而且抽象也不同。它为委托增加了一种间接性。额外的代表团是桥梁。它甚至将适配器与适配接口解耦。它比其他任何包装模式都增加了复杂性,因此请谨慎应用。
构造函数的区别
查看构造函数时,模式差异也很明显。
代理未包装现有对象。构造函数中没有主题。
装饰器和适配器确实包装了已经存在的对象,并且通常
在构造函数中提供。Facade构造函数获取整个对象图的根元素,否则它看起来与 Adapter 相同。
现实生活中的例子——JAXB 编组适配器。此适配器的目的是将一个简单的平面类映射到外部所需的更复杂的结构,并防止使用过多的注释“污染”主题类。
许多 GoF 模式存在大量重叠。它们都建立在多态性的力量之上,有时只是在意图上真正不同。(战略与状态)
阅读Head First Design Patterns后,我对模式的理解增加了 100 倍。
我强烈推荐它!
专家的所有好的答案都已经解释了每种模式代表什么。
我将装饰关键点。
装饰师:
- 在运行时向对象添加行为。继承是实现这一功能的关键,这既是这种模式的优点也是缺点。
- 它修改接口的行为。
例如(带有链接):与&接口java.io
相关的包类InputStream
OutputStream
FileOutputStream fos1 = new FileOutputStream("data1.txt");
ObjectOutputStream out1 = new ObjectOutputStream(fos1);
代理:
- 将它用于延迟初始化,通过缓存对象和控制对客户端/调用者的访问来提高性能。它可以提供替代行为或调用真实对象。在此过程中,它可能会创建新的对象。
- 与允许链接对象的装饰器不同,代理不允许链接。
例如:java.rmi
包类。
适配器:
- 它允许两个不相关的接口通过不同的对象一起工作,可能扮演相同的角色。
- 它修改了原来的界面。
例如java.io.InputStreamReader
(InputStream
返回一个Reader
)
桥:
- 它允许抽象和实现独立变化。
- 它使用组合而不是继承。
例如,集合类在java.util
. List
由 实施ArrayList
。
关键说明:
- 适配器为其主题提供了不同的接口。代理提供相同的接口。装饰器提供了一个增强的接口。
- Adapter改变了对象的接口, Decorator增强了对象的职责。
- Decorator和Proxy用途不同但结构相似
- 适配器在设计完成后就可以工作;Bridge让它们先于它们工作。
- Bridge是预先设计的,让抽象和实现独立变化。改装适配器以使无关的类一起工作
- 装饰器旨在让您无需子类化就可以向对象添加职责。
查看有关各种设计模式示例的出色 SE 问题/文章
这是来自Head First Design Patterns的引述
定义属于书。例子属于我。
装饰者- 不改变界面,但增加责任。假设您有一个汽车接口,当您为不同型号的汽车(s、sv、sl)实现此接口时,您可能需要为某些型号添加更多责任。比如有天窗、安全气囊等。
适配器- 将一个接口转换为另一个。你有一个汽车接口,你希望它像吉普车一样工作。所以你开着车,改装它,然后变成一辆吉普车。因为它不是真正的吉普车。但表现得像吉普车。
Facade - 使界面更简单。假设您有汽车、飞机、船舶接口。实际上,您所需要的只是一门将人们从一个位置发送到另一个位置的课程。您希望外观来决定使用什么车辆。然后,您将所有这些接口引用收集在 1 个保护伞下,并让它决定/委托以保持简单。
Head First:“外观不仅简化了接口,还将客户端与组件子系统解耦。外观和适配器可以包装多个类,但外观的目的是简化,而适配器的目的是将接口转换为不同的东西。 "
所有四种模式都涉及用外部对象/类包装内部对象/类,因此它们在结构上非常相似。我将按目的概述差异:
- 代理将访问封装在外部到内部。
- 装饰器修改或扩展内部与外部的行为。
- 适配器将接口从内部转换为外部。
- Bridge将行为的不变部分(外部)与可变或平台相关部分(内部)分开。
通过内部和外部对象之间的接口变化:
- 在Proxy接口中是相同的。
- 在装饰器接口中是相同的。
- 适配器接口在形式上有所不同,但实现相同的目的。
- 桥接口在概念上是不同的。
我在使用 Web 服务时经常使用它。代理模式可能应该重命名为更实用的东西,比如“包装模式”。我还有一个库,它是 MS Excel 的代理。它可以很容易地自动化 Excel,而不必担心背景细节,比如什么版本已安装(如果有)。
说到细节实现,我发现 Proxy 和 Decorator、Adapter、Facade 之间的区别......在这些模式的常见实现中,有一个目标对象被一个封闭对象包裹。客户端使用封闭对象而不是目标对象。而目标对象实际上在一些封闭对象的方法中起着重要的作用。
但是在Proxy的情况下,封闭对象可以自己播放一些方法,它只是在客户端调用一些需要目标对象参与的方法时初始化目标对象。这就是惰性初始化。在其他模式的情况下,封闭对象实际上是基于目标对象。因此,目标对象始终与构造函数/设置器中的封闭对象一起初始化。
另一件事是,代理与目标完全一样,而其他模式为目标添加了更多功能。
我想为 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,我推荐它作为设计模式的优秀资源。
我相信代码会给出清晰的想法(也可以补充其他答案)。请看下面,(关注类实现和包装的类型)
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);
}
}
}
}
}
设计模式不是数学,它是艺术和软件工程的结合。对于这种要求,您不必使用代理、桥接等。设计模式是为了解决问题而创建的。如果您预计会出现设计问题,请使用它。根据经验,您将了解具体问题,使用哪种模式。如果你擅长扎实的设计原则,你会在不知道它是模式的情况下实现设计模式。常见的例子是战略和工厂模式
因此,更多地关注可靠的设计原则、干净的编码原则和 ttd