我在研究代理模式,感觉它与装饰器、适配器和桥接模式非常相似。我是否有什么误解?它们之间有何区别?为什么要使用代理模式而不是其他模式?你过去如何在实际项目中使用它们?
我在研究代理模式,感觉它与装饰器、适配器和桥接模式非常相似。我是否有什么误解?它们之间有何区别?为什么要使用代理模式而不是其他模式?你过去如何在实际项目中使用它们?
代理、装饰器、适配器和桥接是“包装”类的不同变体。但它们的用途不同。
代理可用于惰性实例化对象,隐藏调用远程服务的事实或控制对对象的访问权限。
装饰器也称为“智能代理”。当您想要向对象添加功能,但不是通过扩展该对象的类型来实现时,可以使用此选项。这允许您在运行时完成此操作。
适配器用于具有抽象接口并希望将该接口映射到具有类似功能角色但不同接口的另一个对象的情况。
桥接与适配器非常相似,但当您定义抽象接口和底层实现时,我们称其为桥接。也就是说,您不是在适应某些遗留或第三方代码,而是所有代码的设计师,但需要能够交换不同的实现。
外观是一个较高级别(更简单)的接口,用于一个或多个类的子系统。假设您拥有一个需要多个对象来表示的复杂概念。对该对象集进行更改非常令人困惑,因为您不总是知道哪个对象具有您需要调用的方法。那就是编写外观以提供针对所有可以对对象集执行的复杂操作的高级方法的时间。例如:学校部分的域模型,具有诸如countStudents()
、reportAttendance()
、assignSubstituteTeacher()
等方法。
因此它们的结构也不同。
代理模式(Proxy)和装饰器模式(Decorator)的接口与它们封装的类型相同,但代理模式在底层创建一个实例,而装饰器模式在构造函数中接受一个实例。
适配器模式(Adapter)和外观模式(Facade)都具有不同于它们所包装的接口的接口。但是适配器模式派生自现有接口,而门面模式创建了一个新接口。
桥接模式(Bridge)和适配器模式(Adapter)都指向现有类型。但是桥接模式将指向抽象类型,而适配器模式可能会指向具体类型。桥接模式将允许您在运行时配对实现,而适配器模式通常不会。
Adapter
通常依赖于一个接口(需要适应另一个接口 - 就像您所说的派生),但它仍然可以创建(引入)一个新的接口(从依赖接口适应而来)。 Adapter
和Facade
之间的区别在于依赖关系的数量,Facade
通常使用大量杂项接口(不仅仅是一个像Adapter
),并将它们组合起来输出一个用于某些目的的通用接口。 - Hopeless我对这个主题的看法。
所有四种模式都有很多共同点,有时都被非正式地称为包装器或包装器模式。它们都使用组合,将主题包装并在某个时候委托执行主题,将一个方法调用映射到另一个方法。它们使客户端不必构造不同的对象并复制所有相关数据。如果明智地使用,它们可以节省内存和处理器。
通过促进松散耦合,它们使曾经稳定的代码不那么容易受到不可避免的更改的影响,并且更易于其他开发人员阅读。
适配器
适配器将主题(被适配者)适应到不同的接口上。这样,我们可以将对象添加到名义上不同类型的集合中。
适配器仅向客户端公开相关方法,可以限制所有其他方法,显示特定上下文的使用意图,例如适应外部库,使其看起来不那么通用,而更专注于我们的应用程序需求。适配器增加了我们代码的可读性和自我描述能力。
适配器可以保护一个团队免受其他团队的易变代码的影响;处理离岸团队时,这是一个救命工具;-)
很少提到的目的是防止主题类过多地使用注释。随着越来越多基于注释的框架的出现,这变得比以往任何时候都更加重要。
适配器有助于解决Java只支持单继承的限制。它可以将多个被适配者组合在一个包装器下,给人一种多重继承的印象。整个装饰器家族的教科书示例都可以在JDK - Java IO中轻松找到。所有类,如BufferedOutputStream,FilterOutputStream和ObjectOutputStream都是OutputStream的装饰器。它们可以像洋葱一样层层包裹,其中一个装饰器再次被装饰,添加更多功能。
代理
代理不是典型的包装器。代理主体-被包装对象,在代理创建时可能尚不存在。代理通常在内部创建它。它可能是按需创建的重量级对象,或者是不同JVM或不同网络节点中的远程对象,甚至是非Java对象,即本地代码中的组件。它不必必须完全包装或委托给另一个对象。
最典型的例子是远程代理、重量级对象初始化程序和访问代理。
远程代理 - 主题位于远程服务器、不同的JVM甚至非Java系统上。代理将方法调用转换为RMI/REST/SOAP调用或其他所需技术,使客户端免受底层技术的影响。
延迟加载代理 - 仅在第一次使用或首次强烈使用时完全初始化对象。
访问代理 - 控制对主题的访问。
外观模式
外观模式与最小知识原则(德米特法则)密切相关。外观模式非常类似于适配器。它们都包装,它们都将一个对象映射到另一个对象,但它们的意图不同。外观模式展开了主题的复杂结构,复杂的对象图,简化了对复杂结构的访问。
外观模式包装了一个复杂的结构,为其提供了一个平面接口。这样可以防止客户端对象暴露在主题结构的内部关系中,从而促进了松散耦合。
桥接模式
适配器模式的更复杂的变体,其中不仅实现有所不同,而且抽象也有所不同。它增加了一层委托的间接性。额外的委托是桥梁。它甚至将适配器与适配接口分离开来。它比其他包装模式更复杂,因此需要谨慎应用。
构造函数的差异
当查看它们的构造函数时,模式之间的差异也很明显。
代理模式没有包装现有对象。构造函数中没有主题。
装饰器模式和适配器模式确实包装了已经存在的对象,并且通常在构造函数中提供。
外观模式的构造函数接受整个对象图的根元素,否则它看起来与适配器模式相同。
现实生活中的例子- JAXB Marshalling Adapter。该适配器的目的是将简单的平面类映射到外部所需的更复杂的结构,并防止过多注释“污染”主题类。
很多 GoF 设计模式之间存在很大的重叠性。它们都建立在多态性的优势上,有时只是意图上稍有不同(例如策略和状态)。
阅读《Head First设计模式》后,我的模式理解能力提高了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
实现。
关键点:
请查看优秀的SE问题/文章,了解各种设计模式的示例
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 { ... }
- 适配器用于当您有一个抽象接口,并且您想将该接口映射到具有类似功能角色但不同接口的另一个对象时。
实现差异:代理、装饰器、适配器
适配器为其主题提供了不同的接口。代理提供相同的接口。装饰器提供增强的接口。
Bridge非常类似于Adapter,但是当您定义抽象接口和底层实现时,我们称其为Bridge。 也就是说,您不是在适应某些遗留或第三方代码,而是您需要能够替换不同的实现, 您设计了所有的代码。
Facade是一个更高级别(即更简单)的接口,用于表示一个或多个类的子系统。 假设您有一个需要多个对象来表示的复杂概念。 对该对象集进行更改会很困惑,因为您并不总是知道哪个对象具有您需要调用的方法。 这就是编写Facade的时间,为所有可对对象集执行的复杂操作提供高级别方法。例如:学校部门的领域模型, 其中包括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);
}
}
}
}
}