Proxy 代理模式

一.意图

代理是一种结构设计模式,允许你为另一个对象提供替代或占位符。代理控制对原始对象的访问,允许你在请求到达原始对象之前或之后执行某些作。

在面向对象系统中,有些对象由于某种原因(比如对象创建的开销很大,或者某些操作需要安全控制,或者需要进程外的访问等),直接访问会给使用者,或者系统结构带来很多麻烦。

对其他对象提供一种代理以控制(隔离,使用接口)对这个对象的访问。------《设计模式》GoF

二.问题

你为什么要控制对某个对象的访问?举个例子:你有一个庞大的物体,消耗了大量系统资源。你偶尔需要它,但并非总是如此。

你可以实现懒惰初始化:只在真正需要时创建这个对象。所有对象客户端都需要执行一些延迟初始化代码。不幸的是,这可能会导致大量代码重复。

理想情况下,我们希望将代码直接放入对象的类中,但这并非总是可行。例如,该类可能是封闭第三方库的一部分。

三.解决方案

代理模式建议你创建一个与原始服务对象接口相同的代理类。然后你更新你的应用,让代理对象传递给原对象的所有客户端。当收到客户端的请求时,代理会创建一个真实的服务对象,并将所有工作委托给它。

但这样做有什么好处呢?如果你需要在类的主逻辑之前或之后执行某件事,代理可以让你做到这一点,而无需更改该类。由于代理实现了与原始类相同的接口,它可以传递给任何期望使用真实服务对象的客户端。

四.现实世界的类比

信用卡是银行账户的代理,银行账户是现金的代理。两者实现了相同的界面:它们可用于支付。消费者会感到安心,因为无需携带大量现金。店主也很满意,因为交易所得的收入会电子化地加入店铺的银行账户,而不会有丢失押金或在去银行途中被抢的风险。

五.结构

六.适合应用场景

有数十种方法可以利用代理模式。让我们来看看最常见的用途。

  1. 懒惰初始化(虚拟代理)。这指的是你有一个重量级的服务对象,它因为一直开着而浪费系统资源,尽管你只是偶尔需要。

    你可以把对象初始化推迟到真正需要的时候,而不是在应用启动时创建对象。

  2. 访问控制(保护代理)。这时你只希望特定客户端能够使用服务对象;例如,当你的对象是作系统的关键部分,客户端是各种启动的应用程序(包括恶意的)。

    代理只有在客户端凭证符合某些条件时,才能将请求转发给服务对象。

  3. 远程服务(远程代理)的本地执行。这指服务对象位于远程服务器上。

    在这种情况下,代理通过网络传递客户端请求,处理与网络合作的所有复杂细节。

  4. 日志请求(日志代理)。这时你需要保留对服务对象的请求历史。

    代理可以在传递给服务之前记录每个请求。

  5. 缓存请求结果(缓存代理)。这时你需要缓存客户端请求的结果并管理缓存的生命周期,尤其是当结果相当大时。

    代理可以为每次请求实现缓存,但这些请求的结果总是相同。代理可以将请求的参数作为缓存键使用。

  6. 很聪明的推荐。这时你需要在没有客户使用时,能够解雇一个重量级物品。

    代理可以跟踪获得服务对象或其结果引用的客户端。代理可能会不时检查客户端,检查它们是否仍然活跃。如果客户端列表为空,代理可能会关闭服务对象并释放底层系统资源。

    代理还可以追踪客户端是否修改了服务对象。然后未更改的对象可以被其他客户端重新利用。

六.实现方式

  1. 如果没有预设的服务接口,就创建一个让代理对象和服务对象可以互换。从服务类中提取接口并不总是可行,因为你需要更改服务的所有客户端才能使用该接口。方案B是让代理成为服务类的子类,这样它就能继承服务的接口。

  2. 创建代理类。它应该有一个字段来存储对该服务的引用。通常,代理负责创建和管理其服务的整个生命周期。极少数情况下,客户端通过构造函数将服务传递给代理。

  3. 根据代理方法的目的来实施。在大多数情况下,代理完成一些工作后,应该将工作委托给服务对象。

  4. 考虑引入一种创建方法,决定客户端是获得代理还是真实服务。这可以是代理类中的简单静态方法,也可以是完整的工厂方法。

  5. 考虑为服务对象实现懒惰初始化。

七.优缺点

  1. 优点:

    • 你可以在客户端不知情的情况下控制服务对象。

    • 当客户端不关心服务对象时,你可以管理服务对象的生命周期。

    • 即使服务对象还没准备好或不可用,代理也能正常工作。

    • 开闭原则。你可以引入新的代理,而无需更换服务或客户端。

  2. 缺点

    • 代码可能会变得更复杂,因为你需要引入很多新类。

    • 服务的回复可能会被延迟。

八.与其他模式的关系

  • 通过适配器,你可以通过不同的接口访问已有的对象。而Proxy的界面保持不变。使用Decorator时,你可以通过增强的界面访问该物品。

  • Facade 与 Proxy 类似,都缓冲复杂实体并独立初始化。与Facade不同,代理与其服务对象具有相同的接口,这使得它们可以互换。

  • 装饰者和代理者结构相似,但意图却大相径庭。这两种模式都建立在构图原则之上,即一个对象应将部分工作委托给另一个。区别在于代理通常独立管理其服务对象的生命周期,而装饰器的组成始终由客户端控制。

九.示例代码

复制代码
#include <iostream>
/**
 * The Subject interface declares common operations for both RealSubject and the
 * Proxy. As long as the client works with RealSubject using this interface,
 * you'll be able to pass it a proxy instead of a real subject.
 */
class Subject {
 public:
  virtual void Request() const = 0;
};
/**
 * The RealSubject contains some core business logic. Usually, RealSubjects are
 * capable of doing some useful work which may also be very slow or sensitive -
 * e.g. correcting input data. A Proxy can solve these issues without any
 * changes to the RealSubject's code.
 */
class RealSubject : public Subject {
 public:
  void Request() const override {
    std::cout << "RealSubject: Handling request.\n";
  }
};
/**
 * The Proxy has an interface identical to the RealSubject.
 */
class Proxy : public Subject {
  /**
   * @var RealSubject
   */
 private:
  RealSubject *real_subject_;
​
  bool CheckAccess() const {
    // Some real checks should go here.
    std::cout << "Proxy: Checking access prior to firing a real request.\n";
    return true;
  }
  void LogAccess() const {
    std::cout << "Proxy: Logging the time of request.\n";
  }
​
  /**
   * The Proxy maintains a reference to an object of the RealSubject class. It
   * can be either lazy-loaded or passed to the Proxy by the client.
   */
 public:
  Proxy(RealSubject *real_subject) : real_subject_(new RealSubject(*real_subject)) {
  }
​
  ~Proxy() {
    delete real_subject_;
  }
  /**
   * The most common applications of the Proxy pattern are lazy loading,
   * caching, controlling the access, logging, etc. A Proxy can perform one of
   * these things and then, depending on the result, pass the execution to the
   * same method in a linked RealSubject object.
   */
  void Request() const override {
    if (this->CheckAccess()) {
      this->real_subject_->Request();
      this->LogAccess();
    }
  }
};
/**
 * The client code is supposed to work with all objects (both subjects and
 * proxies) via the Subject interface in order to support both real subjects and
 * proxies. In real life, however, clients mostly work with their real subjects
 * directly. In this case, to implement the pattern more easily, you can extend
 * your proxy from the real subject's class.
 */
void ClientCode(const Subject &subject) {
  // ...
  subject.Request();
  // ...
}
​
int main() {
  std::cout << "Client: Executing the client code with a real subject:\n";
  RealSubject *real_subject = new RealSubject;
  ClientCode(*real_subject);
  std::cout << "\n";
  std::cout << "Client: Executing the same client code with a proxy:\n";
  Proxy *proxy = new Proxy(real_subject);
  ClientCode(*proxy);
​
  delete real_subject;
  delete proxy;
  return 0;
}

执行结果

复制代码
Client: Executing the client code with a real subject:
RealSubject: Handling request.
​
Client: Executing the same client code with a proxy:
Proxy: Checking access prior to firing a real request.
RealSubject: Handling request.
Proxy: Logging the time of request.
相关推荐
爱编码的傅同学6 小时前
【今日算法】LeetCode 5.最长回文子串 和 287.寻找重复数
算法·leetcode·代理模式
玄冥剑尊20 小时前
动态规划入门
算法·动态规划·代理模式
蜜汁小强3 天前
macOS 上的git代理配置在哪里
git·macos·代理模式·proxy模式
小码过河.3 天前
设计模式——代理模式
设计模式·代理模式
Engineer邓祥浩3 天前
设计模式学习(14) 23-12 代理模式
学习·设计模式·代理模式
IT永勇4 天前
c++设计模式-代理模式
c++·设计模式·代理模式
学嵌入式的小杨同学6 天前
C 语言实战:动态规划求解最长公共子串(连续),附完整实现与优化
数据结构·c++·算法·unity·游戏引擎·代理模式
学嵌入式的小杨同学6 天前
顺序表(SqList)完整解析与实现(数据结构专栏版)
c++·算法·unity·游戏引擎·代理模式
未来龙皇小蓝7 天前
Spring注入Bean流程及其理解
java·spring boot·后端·spring·代理模式