Bootstrap

设计模式Python版 代理模式


前言

GOF设计模式分三大类:

  • 创建型模式:关注对象的创建过程,包括单例模式、简单工厂模式、工厂方法模式、抽象工厂模式、原型模式和建造者模式。
  • 结构型模式:关注类和对象之间的组合,包括适配器模式、桥接模式、组合模式、装饰模式、外观模式、享元模式和代理模式。
  • 行为型模式:关注对象之间的交互,包括职责链模式、命令模式、解释器模式、迭代器模式、中介者模式、备忘录模式、观察者模式、状态模式、策略模式、模板方法模式和访问者模式。

一、代理模式

代理模式(Proxy Pattern)

  • 定义:给某一个对象提供一个代理,并由代理对象控制对原对象的引用。

  • 解决问题:如何提供一种间接访问机制来实现对象的远程访问或受限访问?

  • 使用场景:

    • 当客户端对象需要访问远程主机中的对象时,可以使用远程代理。
    • 当需要用一个消耗资源较少的对象来代表一个消耗资源较多的对象,从而降低系统开销、缩短运行时间时,可以使用虚拟代理
    • 当需要控制对一个对象的访问,为不同用户提供不同级别的访问权限时,可以使用保护代理。
    • 当需要为某一个被频繁访问的操作结果提供一个临时存储空间,以供多个客户端共享访问这些结果时,可以使用缓冲代理
    • 当需要为一个对象的访问(引用)提供一些额外的操作时,可以使用智能引用代理
  • 组成:

    • Subject(抽象主题角色):它声明了真实主题和代理主题的共同接口,客户端通常需要针对抽象主题角色进行编程。
    • Proxy(代理主题角色):代理主题角色内部包含了对真实主题的引用,从而可以在任何时候操作真实主题对象。代理主题角色还可以控制对真实主题的使用,客户端在调用所引用的真实主题操作之前或之后还需要执行其他操作,而不仅仅是单纯调用真实主题对象中的操作。
    • RealSubject(真实主题角色):它定义了代理角色所代表的真实对象,在真实主题角色中实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中定义的操作。
  • 补充说明:

    • 由于某些原因,客户端不想或不能直接访问某个对象,此时可以通过一个被称为“代理”的第三者来实现间接访问,该方案对应的设计模式被称为代理模式。
    • 代理模式为对象的访问控制提供了一种设计方案,而且它具有多种不同的类型,应用相当广泛。
    • 在代理模式中引入了一个新的代理对象,代理对象可以在客户端对象和目标对象之间起到中介的作用,去掉客户不能看到的内容和服务或者增添客户需要的额外服务。
    • 代理模式和装饰模式比较
      • 代理模式主要是给真实主题类增加一些全新的职责,例如权限控制、缓冲处理、智能引用、远程访问等,这些职责与原有职责不属于同一个问题域
      • 装饰模式是通过装饰类为具体构件类增加一些相关的职责,是对原有职责的扩展,这些职责属于同一问题域。
    • 代理模式根据其目的和实现方式不同进行分类
      • 远程代理(Remote Proxy):为一个位于不同的地址空间的对象提供一个本地的代理对象
      • 虚拟代理(Virtual Proxy):如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
      • 保护代理(Protect Proxy):控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
      • 缓冲代理(Cache Proxy):为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
      • 智能引用代理(Smart Reference Proxy):当一个对象被引用时,提供一些额外的操作,例如将对象被调用的次数记录下来等。
    • 代理模式是常用的结构型设计模式之一
  • 优点:

    • 一定程度上降低了系统的耦合度
    • 客户端可以针对抽象主题角色进行编程,增加和更换代理类无须修改源代码,符合开闭原则
    • 远程代理为位于两个不同地址空间对象的访问提供了一种实现机制
    • 虚拟代理通过一个消耗资源较少的对象来代表一个消耗资源较多的对象,可以在一定程度上节省系统的运行开销。
    • 保护代理可以控制对一个对象的访问权限,为不同用户提供不同级别的使用权限。
  • 缺点:

    • 实现代理模式需要额外的工作,有些代理模式的实现可能非常复杂

在这里插入图片描述

二、代理模式示例

使用代理模式设计和实现商务信息查询系统

  • 客户端对象通过代理对象间接访问具有商务信息查询功能的真实对象。在代理对象中除了调用真实对象的商务信息查询功能外,还增加了身份验证和日志记录等功能。
  • ProxySearcher充当代理主题角色,它是查询代理,维持了对RealSearcher对象、AccessValidator对象和Logger对象的引用
  • 属于智能引用代理
"""抽象角色"""


class Searcher:
    def do_search(self, username: str, keyword: str, **kwargs) -> str:
        raise NotImplementedError


"""真实角色"""


class RealSearcher(Searcher):
    # 模拟查询商务信息
    def do_search(self, username, keyword, **kwargs):
        print(f"用户{username},使用关键词{keyword},查询商务信息!")
        return f"返回关于{keyword}具体内容"


"""代理角色"""


class AccessValidator:
    # 身份验证业务类:模拟实现登录验证
    def validate(self, username, passwd) -> bool:
        if username == "张三" and passwd == "123":
            print(f"用户{username}登录成功!")
            return True
        else:
            print(f"用户{username}登录失败!")
            return False


class Logger:
    # 日志记录业务类:模拟实现日志记录
    def log(self, username):
        print(f"更新数据库,用户{username}查询次数加1!")


class ProxySearcher(Searcher):
    # 代理类
    def __init__(self):
        self.searcher = RealSearcher()  # 对真实主题对象的引用
        self.validator = AccessValidator()
        self.logger = Logger()

    def do_search(self, username, keyword, **kwargs):
        # 如身份验证成功,则执行查询
        if not kwargs or "passwd" not in kwargs:
            raise ValueError("未提供身份验证信息!")

        passwd = kwargs.get("passwd")
        if self.validator.validate(username, passwd): # 调用前:验证身份
            result = self.searcher.do_search(username, keyword)  # 调用真实主题对象
            self.logger.log(username)  # 调用后:记录查询日志
            return result
        else:
            return None
  • 客户端代码
searcher: Searcher = ProxySearcher()  # 针对抽象编,可将类名存储在配置文件config.json中
result = searcher.do_search("张三", "人工智能", passwd="123")
print(f"查找结果:{result}")
print("#" * 10)

result = searcher.do_search("张三", "人工智能", passwd="123456")
print(f"查找结果:{result}")
print("#" * 10)

result = searcher.do_search("张三", "人工智能")
print(f"查找结果:{result}")
print("#" * 10)
  • 输出结果
用户张三登录成功!
用户张三,使用关键词人工智能,查询商务信息!
更新数据库,用户张三查询次数加1!
查找结果:返回关于人工智能具体内容
##########
用户张三登录失败!
查找结果:None
##########
Traceback (most recent call last):
...
    raise ValueError("未提供身份验证信息!")
ValueError: 未提供身份验证信息!

三、远程代理

远程代理是一种常用的代理模式,它使得客户端程序可以访问在远程主机上的对象。

  • 客户端对象不能直接访问远程主机中的业务对象,只能采取间接访问的方式。
  • 远程业务对象在本地主机中有一个代理对象,该代理对象负责对远程业务对象的访问和网络通信,它对于客户端对象而言是透明的。
  • 客户端无须关心实现具体业务的是谁,只需要按照服务接口所定义的方式直接与本地主机中的代理对象交互即可。

在这里插入图片描述

四、虚拟代理

虚拟代理也是一种常用的代理模式。

  • 对于一些占用系统资源较多或者加载时间较长的对象,可以给这些对象提供一个虚拟代理。
  • 在真实对象创建成功之前虚拟代理扮演真实对象的替身,而当真实对象创建之后,虚拟代理将用户的请求转发给真实对象。
  • 使用场景
    • 由于对象本身的复杂性或者网络等原因导致一个对象需要较长的加载时间,此时可以用一个加载时间相对较短的代理对象来代表真实对象。
    • 当一个对象的加载十分耗费系统资源的时候,也非常适合使用虚拟代理。
  • 虚拟代理都是用一个“虚假”的代理对象来代表真实对象,通过代理对象来间接引用真实对象,从而在一定程度上提高系统的性能。

五、虚拟代理示例

使用虚拟代理设计图像加载器

  • 代理会在实际需要显示图像时才加载图像,而不是一开始就加载。
  • 实现延迟加载
"""抽象角色"""


class ImageBase:
    def display(self):
        raise NotImplementedError


"""真实角色"""


class Image(ImageBase):
    def __init__(self, filename):
        self.filename = filename
        self.load_image()

    def load_image(self):
        print(f"Loading {self.filename}")
        # 这里假设有一些加载图像的代码
        self.image = "Loaded Image"

    def display(self):
        print(f"Displaying {self.filename}")


"""代理角色"""


class ImageProxy(ImageBase):
    def __init__(self, filename):
        self.filename = filename
        self.image = None

    def display(self):
        if self.image is None:
            self.image = Image(self.filename)
        self.image.display()
  • 客户端代码
image: ImageBase = ImageProxy("《清明上河图》.jpg")
image.display()  # 调用时,再加载图像
image.display()  # 第二次调用时,不会重新加载图像
  • 输出结果
Loading 《清明上河图》.jpg
Displaying 《清明上河图》.jpg
Displaying 《清明上河图》.jpg

您正在阅读的是《设计模式Python版》专栏!关注不迷路~

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;