代理模式Proxy Pattern, since 2024-06-02

(2024.06.02 Sun)
代理模式是结构模式(structural pattern)的一种,为真实对象提供了代替用的接口,扮演中介(intermediary)、代理(surrogate)的角色。代理接收到对真实对象的访问/请求,执行与真实对象相同的功能和动作,可加入额外的功能且不需要修改真实对象。

使用场景

若干可能使用代理模式的场景:

  • 真实对象加载开销过大(expensive object loading):处理复杂或资源密集型(resource-intensive)对象时,使用虚拟代理(virtual proxy),可扮演placeholder的角色,仅在使用时加载对象的全部(load the full object on demand),优化资源使用
  • 远程对象访问:对象在其他地址空间,用户需要以访问本地资源的方式访问,适合建立远程代理(remote proxy),用于管理连接技术细节(如建立网络连接、序列化serialisation、去序列化),使得远程对象用起来像本地对象
  • 增强安全性:保护代理(protection proxy)可增强接入控制,比如加入客户端的授权和验证(authorisation & authentication),保护敏感资源等
  • 智能引用(smart reference):释放不再使用的重型对象(heavyweight object)场景,该代理跟踪活跃用户并周期检查,如果没有客户端使用对象,代理则释放对象和系统资源。也监测对对象的修改,允许在不修改的情况下重用

案例

概念案例 conceptual example

该案例用于解释

  • 代理模式的由哪些类组成
  • 每个类扮演什么角色
  • 模式间的元素如何关联

首先定义一个Subject接口,声明RealSubjectProxy两个类的基本操作。只要使用了该接口的RealSubject类在客户端可用,用户就可以使用Proxy类代替RealSubject类。

from abc import ABC, abstractmethod

class Subject(ABC):
    @abstractmethod
    def request(self) -> None:
        pass

接下来定义RealSubject类,该类中包含核心业务逻辑。而Proxy类与RealSubject有相同接口,可以解决RealSubject中或反应慢或敏感的问题,比如矫正输入数据等。Proxy中最常见的应用包括惰性加载(lazy loading),缓存,控制连接(controlling the access),记录日志等。Proxy可执行应用中的一个,并根据结果进行RealSubject相同的方法

class RealSubject(Subject):
    def request(self) -> None:
        print("RealSubject: Handling request.")

class Proxy(Subject):
    def __init__(self, real_subject: RealSubject) -> None:
        self._real_subject = real_subject

    def request(self) -> None:
        if self.check_access():
            self._real_subject.request()
            self.log_access()

    def check_access(self) -> bool:
        print("Proxy: Checking access prior to firing a real request.")
        return True

    def log_access(self) -> None:
        print("Proxy: Logging the time of request.", end="")

下面是客户单代码,客户端通过Subject接口和所有对象交互。

def client_code(subject: Subject) -> None:
    subject.request()

下面运行这些代码

if __name__ == "__main__":
    print("Client: Executing the client code with a real subject:")
    real_subject = RealSubject()
    client_code(real_subject)
    print("Client: Executing the same client code with a proxy:")
    proxy = Proxy(real_subject)
    client_code(proxy)

运行结果如下

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.

数据库缓存 Caching Proxy for Database Queries

(2024.06.03 Mon at KLN)
该案例中,CacheProxy设计成为客户端与数据库对象RealDatabaseQuery之间的媒介,检测查询结果是否已经进入缓存,并在规定的缓存周期内(specified cache duration)返回缓存结果。

如果缓存已经过期或结果不在缓存中,CacheProxy代理从RealDatabaseQuery中执行查询,将结果推入缓存,并返回给客户端。CacheProxy降低了数据库服务器的负载,通过缓存的方式加速了数据查询的速度和效率。

首先定义数据库接口DatabaseQuery,定义了抽象类的基本方法。

from abc import ABC, abstractmethod
import time

class DatabaseQuery(ABC):
    @abstractmethod
    def execute_query(self, query: str):
        pass

定义数据库类RealDatabaseQuery

class RealDatabaseQuery(DatabaseQuery):
    def execute_query(self, query: str):
        print(f"Execute query: {query}")
        # do something 
        return f"results for query: {query}"

定义数据查询的代理CacheProxy

class CacheProxy(DatabaseQuery):
    def __init__(self, real_database_query: DatabaseQuery, cache_duration: int):
        self._real_database_query = real_database_query
        self._cache_duration = cache_duration
        self._cache = {}

    def execute_query(self, query: str) -> Union[str, int]:
        if query in self._cache and time.time() - self._cache[query]["timestamp"] <= self._cache_duration:
            # results in cache
            print(f"Returning cached results: {query}")
            return self._cache[query]["result"]
        else:
            # perform query in RealDatabaseQuery
            result = self._real_database_query.execute_query(query)
            self._cache[query] = {"result": result, "timestamp": time.time()}
            return result

客户端代码

if __name__ == "__main__":
    real_database_query = RealDatabaseQuery()
    cache_proxy = CacheProxy(real_database_query, 3) 
    # query for the 1st time, will query real database directly
    print(cache_proxy.execute_query("SELECT * FROM table_1")
    print(cache_proxy.execute_query("SELECT * FROM table_2")
    time.sleep(2)
    # query for the 2nd time, will read cache which with shorter response time
    print(cache_proxy.execute_query("SELECT * FROM table_1")
    print(cache_proxy.execute_query("SELECT * FROM table_2")

性能监控代理 Monitoring Proxy for performance metrics

(2024.06.05 Wed)
该代理用于对真实对象的执行性能做统计,记录日志且不会影响真实对象的功能。该代理可惰性实例化(lazily instantiate)真实对象,确保系统资源被保留到对象运行之时。

定义接口

from abc import ABC, abstractmethod
from datetime import datetime

class Subject(ABC):
    @abstractmethod
    def perform_operation(self):
        pass

定义真实对象

class RealSubject(Subject):
    def perform_operation(self):
        """
        The Real Subject's method representing a costly operation.
        """
        print("RealSubject: Performing a costly operation...")

定义代理对象

class MonitoringProxy(Subject):
    def __init__(self):
        self._real_subject = None

    def perform_operation(self):
        """
        The Proxy's method, which monitors and adds performance metrics.
        Lazily instantiates the Real Subject on the first call.
        """
        if self._real_subject is None:
            print("MonitoringProxy: Lazy loading the RealSubject...")
            self._real_subject = RealSubject()
        
        start_time = datetime.now()
        print(
          f"MonitoringProxy: Recording operation start time - {start_time}"
        )
        
        # Delegate the operation to the Real Subject
        self._real_subject.perform_operation()
        
        end_time = datetime.now()
        execution_time = end_time - start_time
        print(f"MonitoringProxy: Recording operation end time - {end_time}")
        print(
          f"MonitoringProxy: Operation executed in {execution_time} seconds"
        )

定义客户端

if __name__ == "__main__":
    monitoring_proxy = MonitoringProxy()
    monitoring_proxy.perform_operation()

当代理对象调用perform_operation方法时,MonitoringProxy记录运行的开始和结束时间,以计算总运行时间,且无需改变RealSubject对象的行为。

其他应用案例

  • Virtual Proxy for Image Loading: In photo editing apps, a Virtual Proxy represents high-resolution images, loading them on demand, optimizing memory, and enhancing startup times.

  • Remote Proxy for Web Services: When dealing with remote web services or APIs, a Remote Proxy simplifies communication, handling authentication, encryption, and network connectivity details.

  • Lazy Loading for Expensive Objects: In large datasets or complex objects, use a Proxy to delay object creation until necessary, common in CAD software.

  • Access Control with Security Proxy: Ensure secure data access by employing a Security Proxy, enforcing permissions and authentication checks.

  • Logging Proxy for Debugging: Debugging is streamlined with a Logging Proxy recording method calls and parameters, aiding issue diagnosis without code modifications.

  • Counting Proxy for Resource Management: In resource-critical systems like concurrent programming, a Counting Proxy tracks object references, releasing them when no longer required.

  • Load Balancing with Proxy Servers: Proxy servers in web apps distribute network requests across backend servers, ensuring availability and load balancing.

  • Transactional Proxy for Database Operations: Database apps benefit from a Transactional Proxy handling transactions, ensuring data integrity through rollback and commit mechanisms.

Reference

1 medium,Design Patterns in Python: Proxy, The Powerful Surrogate, by Amir Lavasani
2 Refactoring Guru, Proxy in Python

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,137评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,824评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,465评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,131评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,140评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,895评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,535评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,435评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,952评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,081评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,210评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,896评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,552评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,089评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,198评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,531评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,209评论 2 357

推荐阅读更多精彩内容