您能否给出任何好的解释Proxy和Decorator之间的区别是什么?
我看到的主要区别是,当我们假设代理使用组合而装饰器使用聚合时,似乎很明显,通过使用多个(一个或多个)装饰器,您可以修改/添加功能到预先存在的实例(装饰),而代理有自己的代理类的内部实例,并委托给它添加一些附加功能(代理行为)。
问题是 -使用聚合创建的Proxy仍然是Proxy还是Decorator?是否允许(根据 GoF 模式中的定义)创建具有聚合的代理?
您能否给出任何好的解释Proxy和Decorator之间的区别是什么?
我看到的主要区别是,当我们假设代理使用组合而装饰器使用聚合时,似乎很明显,通过使用多个(一个或多个)装饰器,您可以修改/添加功能到预先存在的实例(装饰),而代理有自己的代理类的内部实例,并委托给它添加一些附加功能(代理行为)。
问题是 -使用聚合创建的Proxy仍然是Proxy还是Decorator?是否允许(根据 GoF 模式中的定义)创建具有聚合的代理?
真正的区别不是所有权(组合与聚合),而是类型信息。
装饰器总是通过它的委托人。代理 可能会自己创建它,或者他可能会注入它。
但是代理 总是知道被委托者的(更多)特定类型。换句话说,Proxy和它的被委托者将具有相同的基类型,但Proxy指向某个派生类型。装饰器指向它自己的基本类型。因此,不同之处在于关于受委托者类型的编译时信息。
在动态语言中,如果被委托者被注入并且恰好具有相同的接口,则没有区别。
您的问题的答案是“是”。
装饰器模式侧重于向对象动态添加功能,而代理模式侧重于控制对对象的访问。
编辑:-
代理和真实主体之间的关系通常在编译时设置,代理以某种方式实例化它,而装饰器在运行时分配给主体,只知道主体的接口。
装饰器获取装饰对象的引用(通常通过构造函数),而代理负责自己做。
代理可能根本不实例化包装对象(如果不使用对象字段/获取器,像这样做 ORM 以防止对 DB 的不必要访问),而装饰器始终保持到实际包装实例的链接。
代理通常由框架用于添加安全性或缓存/延迟并由框架构建(而不是由常规开发人员本身)。
装饰器通常用于由开发人员自己基于接口而不是实际类向旧类或遗留类添加新行为(因此它适用于广泛的接口实例,代理围绕具体类)。
这是来自 GoF 的直接引用(第 216 页)。
尽管装饰器可以具有与代理类似的实现,但装饰器有不同的用途。装饰器向对象添加一个或多个职责,而代理控制对对象的访问。
代理的实现程度各不相同,就像装饰器一样。保护代理的实现可能与装饰器完全一样。另一方面,远程代理将不包含对其真实主体的直接引用,而仅包含间接引用,例如“主机 ID 和主机上的本地地址”。虚拟代理将从文件名等间接引用开始,但最终将获得并使用直接引用。
流行的答案表明代理知道其委托的具体类型。从这句话我们可以看出,这并不总是正确的。
根据 GoF,Proxy 和 Decorator 的区别在于 Proxy限制了客户端。装饰器没有。代理可以通过控制对功能的访问来限制客户端的操作;或者它可能会通过执行客户端不可见和未知的操作来限制客户端知道的内容。Decorator 做相反的事情:它以一种对客户可见的方式增强了它的委托所做的事情。
我们可以说 Proxy 是一个黑盒子,而 Decorator 是一个白盒子。
在对比 Proxy 和 Decorator 时,wrapper 和 delegate 之间的组合关系是错误的关系,因为组合是这两种模式的共同特征。包装器和客户端之间的关系是区分这两种模式的原因。
主要区别:
Sourcemaking文章以出色的方式引用了相似之处和不同之处。
相关 SE 问题/链接:
Proxy 和 Decorator 的目的不同,它们侧重于内部实现。代理用于使用远程、跨进程或跨网络对象,就好像它是本地对象一样。装饰器用于向原始界面添加新行为。
虽然这两种模式在结构上相似,但代理的大部分复杂性在于确保与源对象的正确通信。另一方面,装饰器专注于添加行为的实现。
花了一段时间才弄清楚这个答案以及它的真正含义。举几个例子应该更清楚。
Proxy
第一的:
public interface Authorization {
String getToken();
}
和 :
// goes to the DB and gets a token for example
public class DBAuthorization implements Authorization {
@Override
public String getToken() {
return "DB-Token";
}
}
并且有一个调用者 this Authorization
,一个非常愚蠢的调用者:
class Caller {
void authenticatedUserAction(Authorization authorization) {
System.out.println("doing some action with : " + authorization.getToken());
}
}
到目前为止没有什么不寻常的,对吧?从某个服务获取令牌,使用该令牌。现在对图片又多了一个要求,添加日志记录:意思是每次都记录令牌。对于这种情况很简单,只需创建一个Proxy
:
public class LoggingDBAuthorization implements Authorization {
private final DBAuthorization dbAuthorization = new DBAuthorization();
@Override
public String getToken() {
String token = dbAuthorization.getToken();
System.out.println("Got token : " + token);
return token;
}
}
我们将如何使用它?
public static void main(String[] args) {
LoggingDBAuthorization loggingDBAuthorization = new LoggingDBAuthorization();
Caller caller = new Caller();
caller.authenticatedUserAction(loggingDBAuthorization);
}
请注意,它LoggingDBAuthorization
包含一个DBAuthorization
. 两者兼而有之。LoggingDBAuthorization
_DBAuthorization
Authorization
DBAuthorization
基本接口 ( ) 的一些具体实现 ( Authorization
)。换句话说,代理确切地知道正在代理什么。Decorator
:
它的开头与 几乎相同Proxy
,带有一个界面:
public interface JobSeeker {
int interviewScore();
}
及其实现:
class Newbie implements JobSeeker {
@Override
public int interviewScore() {
return 10;
}
}
现在我们想添加一个更有经验的候选人,将它的面试分数加上另一个JobSeeker
:
@RequiredArgsConstructor
public class TwoYearsInTheIndustry implements JobSeeker {
private final JobSeeker jobSeeker;
@Override
public int interviewScore() {
return jobSeeker.interviewScore() + 20;
}
}
注意我是怎么说的,加上另一个求职者的,不是 Newbie
。ADecorator
并不确切知道它在装饰什么,它只知道该装饰实例的契约(它知道关于JobSeeker
)。请注意,这与Proxy
;不同。相比之下,它确切地知道它在装饰什么。
您可能会质疑在这种情况下这两种设计模式之间是否真的有任何区别?如果我们尝试将 写成Decorator
怎么Proxy
办?
public class TwoYearsInTheIndustry implements JobSeeker {
private final Newbie newbie = new Newbie();
@Override
public int interviewScore() {
return newbie.interviewScore() + 20;
}
}
这绝对是一种选择,并强调了这些模式的接近程度;如其他答案中所述,它们仍然适用于不同的场景。
ADecorator
为对象增加了额外的责任,而代理控制对对象的访问,它们都使用组合。如果您的包装类与主题混淆,那么它显然是一个代理。让我通过PHP中的代码示例来解释:
给出的是以下内容CarRepository
:
interface CarRepositoryInterface
{
public function getById(int $id) : Car
}
class CarRepository implements CarRepositoryInterface
{
public function getById(int $id) : Car
{
sleep(3); //... fake some heavy db call
$car = new Car;
$car->setId($id);
$car->setName("Mercedes Benz");
return $car;
}
}
Proxy
AProxy
通常用作延迟加载或缓存代理:
class CarRepositoryCacheProxy implements CarRepositoryInterface
{
private $carRepository;
private function getSubject() : CarRepositoryInterface
{
if($this->carRepository == null) {
$this->carRepository = new CarRepository();
}
return $this->carRepository;
}
/**
* This method controls the access to the subject
* based on if there is cache available
*/
public function getById(int $id) : Car
{
if($this->hasCache(__METHOD__)) {
return unserialize($this->getCache(__METHOD__));
}
$response = $this->getSubject()->getById($id);
$this->writeCache(__METHOD__, serialize($response));
return $response;
}
private function hasCache(string $key) : bool
{
//... implementation
}
private function getCache(string $key) : string
{
//... implementation
}
private function writeCache(string $key, string $result) : string
{
//... implementation
}
}
Decorator
Decorator
只要添加的行为不“控制”主题,就可以使用A :
class CarRepositoryEventManagerDecorator implements CarRepositoryInterface
{
private $subject, $eventManager;
/**
* Subjects in decorators are often passed in the constructor,
* where a proxy often takes control over the invocation behavior
* somewhere else
*/
public function __construct(CarRepositoryInterface $subject, EventManager $eventManager)
{
$this->subject = $subject;
$this->eventManager = $eventManager;
}
public function getById(int $id) : Car
{
$this->eventManager->trigger("pre.getById");
//this method takes no control over the subject
$result = $this->subject->getById($id);
$this->eventManager->trigger("post.getById");
return $result;
}
}
让我先解释一下模式,然后再问你问题。
从类图和含义来看,它们非常相似:
但它们有一些区别:
不同的意图:代理使用与其受委托者完全不同的领域知识来增强受委托者(传递的对象)的行为。例如,安全代理添加受委托者的安全控制。发送远程消息的代理需要序列化/反序列化数据并具有网络接口知识,但与如何准备源数据无关。装饰器帮助受委托人处理的同一问题域。例如,BufferedInputStreaman(一个 IO 装饰器)在输入上工作,这与它的受托者是同一个问题域(IO),但如果没有提供 IO 数据的受托者,它就无法执行。
依赖强与否:装饰器依赖委托完成行为,没有委托(强)就无法完成行为。因此,我们总是使用聚合而不是合成。即使不需要委托人(弱),代理也可以执行伪造的行为。例如,mockito(单元测试框架)可以仅通过其接口来模拟/监视行为。因此,我们使用合成来表示对真实对象没有强依赖性。
多次增强(如问题所述):代理:我们可以利用代理来包装真实对象一次而不是多次。装饰器:装饰器可以多次包裹真实对象,也可以包裹已经被装饰器包裹的对象(可以是不同的装饰器,也可以是相同的装饰器)。例如,对于订单系统,您可以使用装饰器进行折扣。PercentageDiscountDecorator 就是砍掉 50%,而 DeductionAmountDiscountDecorator 就是如果金额大于 10$() 就直接扣 5$。所以,1)。当您想减免 50% 并扣除 5 美元时,您可以这样做:new DeductionAmountDiscountDecorator(new PercentageDiscountDecorator(delegee)) 2)。当您想扣除 10 美元时,您可以执行 new DeductionAmountDiscountDecorator(new DeductionAmountDiscountDecorator(delegee))。
问题的答案与Proxy和Decorator的区别无关。为什么?
如果您指定的问题与 Proxy 和 Decorator 处理的问题确实不同,并且确实需要聚合,为什么不使用呢?我认为将 OO 应用于您的问题比您将其标记为代理或装饰器更重要。
Proxy为被包装的对象提供了相同的接口,Decorator为其提供了增强的接口,Proxy 通常自己管理其服务对象的生命周期,而 Decorator 的组成始终由客户端控制。