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()