代理模式

92 阅读5分钟

1. 代理模式简介

代理模式是一种设计模式,它允许通过一个代理对象来控制对另一个对象的访问,从而提供了一种间接的调用方式。

2. 代理模式的详细介绍

2.1 出现背景

当需要在客户端和实际服务端对象之间添加一些控制逻辑,如访问控制、日志记录、性能优化等,而又不想修改原有的客户端或服务端代码时,就可以考虑使用代理模式。

2.2 模式结构

代理模式通常包括以下几个参与者:

  • 抽象主题(Subject) :定义了代理和被代理共享的接口。
  • 具体主题(ConcreteSubject) :实现了抽象主题的具体行为。
  • 代理(Proxy) :实现了抽象主题,并持有一个对具体主题的引用,用来控制对具体主题的访问。
  • 客户端(Client) :使用代理来操作具体主题。

2.3 调用代理模式相关的对象

客户端发起对代理的请求,代理接收这个请求并进行处理,然后代理将请求转发给具体主题,具体主题处理后返回结果给代理,最后代理将结果返回给客户端。

3. 代理模式的优缺点及使用场景

  • 优点

    • 增加了系统的灵活性和可扩展性。
    • 可以在代理层上添加通用的操作,减少了代码的重复。
    • 可以控制对具体主题的访问。
  • 缺点

    • 增加了系统的复杂度。
    • 可能会影响系统的性能。
    • 如果代理层设计不当,可能会导致系统错误或性能下降。
  • 使用场景

    • 远程代理:当实际的远程对象不可直接访问或访问代价较大时。
    • 虚拟代理:当需要创建一个资源消耗者很高的对象时。
    • 保护代理:当不希望客户端直接实例化一个对象,或者当一个对象需要被其他对象保护起来时。
    • 缓存代理:当一个对象的访问非常频繁,但是创建成本又很高时。
    • 懒加载代理:当一个对象在初始化时需要大量的时间和资源,但是在某些时候可能根本不会被使用时。

4. 代理模式的Python实现示例

下面的例子演示如何使用代理模式在第三方腾讯视频 (TencentVideo, 代码示例中记为 TV) 程序库中添加延迟初始化和缓存:

# 远程服务接口。
from abc import ABC, abstractmethod

class ThirdPartyTVLib(ABC):
   @abstractmethod
   def listVideos(self):
       pass

   @abstractmethod
   def getVideoInfo(self, id):
       pass

   @abstractmethod
   def downloadVideo(self, id):
       pass

# 服务连接器的具体实现。该类的方法可以向腾讯视频请求信息。请求速度取决于
# 用户和腾讯视频的互联网连接情况。如果同时发送大量请求,即使所请求的信息
# 一模一样,程序的速度依然会减慢。
class ThirdPartyTVClass(ThirdPartyTVLib):
   def listVideos(self):
       # 向腾讯视频发送一个 API 请求。
       pass

   def getVideoInfo(self, id):
       # 获取某个视频的元数据。
       pass

   def downloadVideo(self, id):
       # 从腾讯视频下载一个视频文件。
       pass

# 为了节省网络带宽,我们可以将请求结果缓存下来并保存一段时间。但你可能无法直接
# 将这些代码放入服务类中。比如该类可能是第三方程序库的一部分或其签
# 名是 `final`。因此我们会在一个实现了服务类接口的新代理类中放入
# 缓存代码。当代理类接收到真实请求后,才会将其委派给服务对象。
class CachedTVClass(ThirdPartyTVLib):
   def __init__(self, service: ThirdPartyTVLib):
       self._service = service
       self._listCache = None
       self._videoCache = None
       self._needReset = False

   def listVideos(self):
       if self._listCache is None or self._needReset:
           self._listCache = self._service.listVideos()
       return self._listCache

   def getVideoInfo(self, id):
       if self._videoCache is None or self._needReset:
           self._videoCache = self._service.getVideoInfo(id)
       return self._videoCache

   def downloadVideo(self, id):
       if not self.downloadExists(id) or self._needReset:
           self._service.downloadVideo(id)

   @staticmethod
   def downloadExists(id):
       # 这里应该是检查本地是否已经存在该id的视频下载
       pass

# 之前直接与服务对象交互的 GUI 类不需要改变,前提是它仅通过接口与服务对象
# 交互。我们可以安全地传递一个代理对象来代替真实服务对象,因为它们都实
# 现了相同的接口。
class TVManager:
   def __init__(self, service: ThirdPartyTVLib):
       self._service = service

   def renderVideoPage(self, id):
       info = self._service.getVideoInfo(id)
       # 渲染视频页面。
       pass

   def renderListPanel(self):
       list_ = self._service.listVideos()
       # 渲染视频缩略图列表。
       pass

   def reactOnUserInput(self):
       self.renderVideoPage()
       self.renderListPanel()

# 程序可在运行时对代理进行配置。
class Application:
   def init(self):
       self._aTVService = ThirdPartyTVClass()
       self._aTVProxy = CachedTVClass(self._aTVService)
       self._manager = TVManager(self._aTVProxy)
       self._manager.reactOnUserInput()

5. 简单练习:设计模式专题之代理模式】7-小明买房子

""" 
【设计模式专题之代理模式】7-小明买房子
时间限制:1.000S  空间限制:256MB
题目描述
小明想要购买一套房子,他决定寻求一家房屋中介来帮助他找到一个面积超过100平方米的房子,只有符合条件的房子才会被传递给小明查看。
输入描述
第一行是一个整数 N(1 ≤ N ≤ 100),表示可供查看的房子的数量。

接下来的 N 行,每行包含一个整数,表示对应房子的房屋面积。

输出描述
对于每个房子,输出一行,表示是否符合购房条件。如果房屋面积超过100平方米,输出 "YES";否则输出 "NO"。
输入示例
3
120
80
110
输出示例
YES
NO
YES
"""

from abc import ABC, abstractmethod

class House(ABC):
    @abstractmethod
    def purchase(self, area: int):
        raise NotImplemented
        
class SuitableHouse(House):
    def purchase(self, area: int):
        assert area >= 100
        print("YES")
        
class HouseAgent(House):
    def __init__(self):
        self.house = SuitableHouse()
    
    def purchase(self, area: int):
        if area >= 100:
            self.house.purchase(area)
        else:
            print("NO")
            
def client():
    agent = HouseAgent()
    n = int(input())
    for _ in range(n):
        area = int(input())
        agent.purchase(area)
        
if __name__ == "__main__":
    client()

6. 参考文章