设计模式(C++)详解——代理模式 (Proxy Pattern)(1)

<摘要>

代理模式是一种结构型设计模式,其核心意图是为其他对象提供一种代理以控制对这个对象的访问。它通过引入一个与被代理对象实现相同接口的代理类,在客户端和目标对象之间架设一道中间层,从而实现对目标对象访问的间接控制。这种控制可以体现在多个方面:延迟加载高开销对象(虚拟代理)、添加权限验证(保护代理)、简化复杂系统调用(远程代理)、或管理资源生命周期(智能引用代理)。代理模式是面向对象设计中"开闭原则"和"单一职责原则"的经典体现,它将核心业务功能与辅助性管控逻辑分离,使得系统更易于扩展和维护。在现代软件开发中,从ORM框架的延迟加载到分布式系统的RPC stub,再到C++的智能指针,代理模式的应用无处不在,是构建灵活、健壮软件架构的重要工具之一。


<解析>

1. 背景与核心概念

1.1 起源与发展

代理模式的概念并非凭空产生,它源于现实世界以及早期计算机科学中对"中介"或"代表"的抽象需求。在法律领域,律师是当事人的代理;在商业中,经纪人是明星或作家的代理。这些代理负责处理访问、谈判、日程安排等事务,从而保护或简化被代理对象的职责。

在软件领域,代理模式的形式化描述最早出现在著名的"四人帮"(GoF)的著作《设计模式:可复用面向对象软件的基础》(1994年)中,并被归类为结构型模式。其设计初衷是为了解决直接访问某些对象时带来的问题,例如:

  • 性能问题:某些对象创建开销极大,不需要在程序启动时就完全初始化。
  • 权限问题:客户端的访问需要被验证和授权。
  • 复杂性隐藏:对象位于远程地址空间或复杂子系统之后,直接访问不便。
  • 增强功能:需要在对象访问前后添加额外的逻辑(如日志、引用计数)。

随着软件架构的发展,尤其是分布式系统(如CORBA, Java RMI, Web Services)和大型单体应用(如基于ORM的企业应用)的兴起,代理模式的应用变得更加广泛和关键。如今,它已是众多框架和库(如Spring AOP, Hibernate, STL)的基石之一。

1.2 核心概念与术语

代理模式的核心在于"控制访问",而非"增强功能"(这与装饰器模式不同)。它包含三个核心角色,其UML类图如下所示:
uses <<interface>> Subject +Request() RealSubject +Request() Proxy -realSubject: RealSubject +Request()

  • Subject (抽象主题角色)

    • 定义RealSubjectProxy的共用接口。
    • 这样,任何使用RealSubject的地方都可以透明地使用Proxy
    • 通常以抽象类或接口的形式存在。
  • RealSubject (真实主题角色)

    • 定义了代理所代表的真实对象。
    • 是最终要引用的对象,包含了业务逻辑的核心功能。
    • 客户端通常直接调用其方法以完成实际工作。
  • Proxy (代理主题角色)

    • 持有对RealSubject的引用(或可以创建/获取它)。
    • 实现与RealSubject相同的接口(继承自Subject)。
    • 在调用RealSubjectRequest()方法前后,可以执行额外的控制操作,如延迟加载、访问控制、日志记录等。

关键术语:

  • 透明性 (Transparency) :客户端认为它是在直接操作RealSubject,并不知道代理的存在。这是代理模式追求的理想效果。
  • 间接性 (Indirection):代理在客户端和真实对象之间提供了一层间接层,这层间接层是所有控制逻辑发生的地方。
1.3 代理类型

根据目的和场景的不同,代理模式有多种常见的实现类型:

代理类型 目的 典型应用场景
虚拟代理 (Virtual Proxy) 延迟昂贵对象的创建或加载,直到真正需要它时。 大型图片的延迟加载、ORM框架中的延迟加载(Lazy Loading)。
保护代理 (Protection Proxy) 控制对真实对象的访问权限。 基于角色的访问控制(RBAC)系统。
远程代理 (Remote Proxy) 为位于不同地址空间(远程服务器)的对象提供本地代表。 RPC(远程过程调用)、RMI(远程方法调用)、Web Service客户端存根(Stub)。
智能引用代理 (Smart Reference) 在访问对象时执行额外的内务处理操作。 智能指针(如std::shared_ptr)、对象引用计数、线程安全控制、懒初始化。
缓存代理 (Cache Proxy) 为开销大的运算结果提供临时存储。 Web服务器反向代理缓存、数据库查询缓存。

2. 设计意图与考量

2.1 核心目标与设计理念

代理模式的根本意图是:在不直接操作目标对象的前提下,通过一个代理对象来间接地控制和管理对该目标对象的访问

其背后的设计理念遵循了多个面向对象设计原则:

  1. 开闭原则 (Open/Closed Principle) :客户端代码依赖于抽象的Subject接口,对扩展开放(可以引入新的代理类型),对修改关闭(无需修改操作Subject的客户端代码)。
  2. 单一职责原则 (Single Responsibility Principle) :将"管理目标对象的访问逻辑"与"目标对象自身的核心业务逻辑"分离。RealSubject只关心如何完成工作,而Proxy则关心何时、以何种条件、何种方式去使用RealSubject
  3. 最少知识原则 (Law of Demeter):客户端只需与代理交互,而不需要了解背后真实对象和访问控制逻辑的复杂细节,降低了耦合度。
2.2 权衡因素

引入代理模式并非没有代价,设计时需要权衡以下因素:

  • 优点

    • 职责清晰:隔离了控制逻辑和业务逻辑,系统更具可维护性。
    • 高扩展性:可以轻松地增加新的代理,而不影响客户端和真实主题。
    • 更好的安全性和灵活性:通过保护代理可以方便地增加权限管理。
    • 性能优化:通过虚拟代理和缓存代理可以提升系统整体性能。
  • 缺点

    • 系统复杂度增加:引入了新的代理层,可能会使系统设计更复杂。
    • 请求速度可能变慢:由于增加了中间层,请求的转发和处理会导致轻微的延迟。但在虚拟代理等场景下,整体性能反而是提升的。
    • 开发难度:需要为服务接口创建代理类,特别是在没有代码生成工具辅助时,工作量会增加。

与装饰器模式的区别

这是一个常见的困惑点。两者在结构上非常相似,但意图不同

  • 代理模式 的目的是控制访问。代理通常知道被代理对象的生命周期,并决定是否、何时创建和调用它。关系通常是确定的(一个代理对应一个真实主题)。
  • 装饰器模式 的目的是增强功能。装饰器为对象添加新的职责,它对被装饰对象是透明的。装饰器可以层层嵌套,动态地添加任意多个功能。

简单来说:代理是"经纪人",控制你见不见明星;装饰是"造型师",负责给明星(或另一个造型师)做造型。

3. 实例与应用场景

下面我们通过三个代表性的案例来具体说明代理模式的应用。

案例一:虚拟代理实现大型图片延迟加载

应用场景:在图形界面应用程序中,如果一个窗口包含多张大型图片,一次性加载所有图片会导致启动缓慢,用户体验差。我们希望仅当图片需要显示(滚动到视口内)时,才从磁盘加载它。

cpp 复制代码
#include <iostream>
#include <string>
#include <memory>

// 1. 抽象主题角色 (Subject)
class Image {
public:
    virtual ~Image() = default;
    virtual void display() = 0; // 显示图片的通用接口
};

// 2. 真实主题角色 (RealSubject)
class RealImage : public Image {
public:
    RealImage(const std::string& filename) : m_filename(filename) {
        loadFromDisk(); // 构造函数中直接加载,开销大
    }

    void display() override {
        std::cout << "Displaying " << m_filename << std::endl;
    }

private:
    void loadFromDisk() {
        std::cout << "Loading... " << m_filename << std::endl; // 模拟耗时的磁盘I/O操作
        // ... 实际从磁盘加载图片数据的代码 ...
    }

    std::string m_filename;
};

// 3. 代理角色 (Proxy)
class ProxyImage : public Image {
public:
    ProxyImage(const std::string& filename) : m_filename(filename), m_realImage(nullptr) {
        // 代理构造时,并不立即创建真实的图片对象
        std::cout << "Proxy created for " << m_filename << std::endl;
    }

    ~ProxyImage() {
        delete m_realImage; // 释放资源
    }

    void display() override {
        // 延迟初始化:直到真正需要显示时,才创建真实对象
        if (m_realImage == nullptr) {
            m_realImage = new RealImage(m_filename);
        }
        // 调用真实对象的显示方法
        m_realImage->display();
    }

private:
    std::string m_filename;
    RealImage* m_realImage; // 持有对真实对象的引用
};

// 客户端代码
int main() {
    // 创建代理时,RealImage还未创建,启动快
    ProxyImage proxyImage1("high_res_photo1.jpg");
    ProxyImage proxyImage2("high_res_photo2.jpg");

    // 模拟用户交互,只有需要显示时才加载真实图片
    std::cout << "--- User scrolls to see image1 ---" << std::endl;
    proxyImage1.display(); // 此时才会加载第一张图片

    std::cout << "--- User scrolls to see image2 ---" << std::endl;
    proxyImage2.display(); // 此时才会加载第二张图片

    // 再次显示,不会再加载,直接使用已创建的对象
    std::cout << "--- User scrolls back to image1 ---" << std::endl;
    proxyImage1.display();

    return 0;
}

输出结果:

复制代码
Proxy created for high_res_photo1.jpg
Proxy created for high_res_photo2.jpg
--- User scrolls to see image1 ---
Loading... high_res_photo1.jpg
Displaying high_res_photo1.jpg
--- User scrolls to see image2 ---
Loading... high_res_photo2.jpg
Displaying high_res_photo2.jpg
--- User scrolls back to image1 ---
Displaying high_res_photo1.jpg

流程时序图:
Client Proxy RealImage ProxyImage("photo1.jpg") 创建代理,RealImage未初始化 display() new RealImage("photo1.jpg") 耗时:从磁盘加载 display() display() // 第二次调用 display() // 直接调用已存在对象 Client Proxy RealImage

案例二:保护代理实现权限控制

应用场景 :在一个文档管理系统中,不同的用户对文档(Document)拥有不同的操作权限(如查看、修改)。我们需要在执行操作前验证当前用户的权限。

cpp 复制代码
#include <iostream>
#include <string>

// 1. 抽象主题角色
class Document {
public:
    virtual ~Document() = default;
    virtual void view() = 0;   // 查看文档
    virtual void edit() = 0;   // 编辑文档
};

// 2. 真实主题角色
class RealDocument : public Document {
public:
    RealDocument(const std::string& title, const std::string& content)
        : m_title(title), m_content(content) {}

    void view() override {
        std::cout << "Viewing Document: " << m_title << std::endl;
        std::cout << "Content: " << m_content << std::endl;
    }

    void edit() override {
        std::cout << "Editing Document: " << m_title << std::endl;
        std::cout << "Please enter new content: ";
        std::getline(std::cin, m_content); // 模拟编辑操作
        std::cout << "Document saved." << std::endl;
    }

private:
    std::string m_title;
    std::string m_content;
};

// 3. 代理角色 (保护代理)
class ProtectionProxy : public Document {
public:
    // 构造函数传入真实对象和当前用户角色
    ProtectionProxy(std::shared_ptr<RealDocument> doc, const std::string& userRole)
        : m_realDocument(doc), m_userRole(userRole) {}

    void view() override {
        // 任何人都可以查看
        std::cout << "[Protection Proxy] Permission check for 'view'... ";
        if (hasPermission("view")) {
            std::cout << "Granted." << std::endl;
            m_realDocument->view();
        } else {
            std::cout << "Denied." << std::endl;
            std::cout << "You need 'view' permission." << std::endl;
        }
    }

    void edit() override {
        // 只有编辑者或管理员可以编辑
        std::cout << "[Protection Proxy] Permission check for 'edit'... ";
        if (hasPermission("edit")) {
            std::cout << "Granted." << std::endl;
            m_realDocument->edit();
        } else {
            std::cout << "Denied." << std::endl;
            std::cout << "You need 'edit' permission." << std::endl;
        }
    }

private:
    bool hasPermission(const std::string& action) const {
        // 简单的权限验证逻辑
        if (action == "view") {
            return true; // 所有人都可以查看
        } else if (action == "edit") {
            return (m_userRole == "editor" || m_userRole == "admin");
        }
        return false;
    }

    std::shared_ptr<RealDocument> m_realDocument;
    std::string m_userRole;
};

// 客户端代码
int main() {
    // 创建一个真实文档
    auto realDoc = std::make_shared<RealDocument>("Annual Report", "Confidential data...");

    // 为不同用户创建代理
    ProtectionProxy readerProxy(realDoc, "reader");
    ProtectionProxy editorProxy(realDoc, "editor");

    std::cout << "=== Reader trying to access ===" << std::endl;
    readerProxy.view();
    readerProxy.edit(); // 这里会被拒绝

    std::cout << "\n=== Editor trying to access ===" << std::endl;
    editorProxy.view();
    editorProxy.edit(); // 这里会被允许

    return 0;
}

输出结果:

复制代码
=== Reader trying to access ===
[Protection Proxy] Permission check for 'view'... Granted.
Viewing Document: Annual Report
Content: Confidential data...
[Protection Proxy] Permission check for 'edit'... Denied.
You need 'edit' permission.

=== Editor trying to access ===
[Protection Proxy] Permission check for 'view'... Granted.
Viewing Document: Annual Report
Content: Confidential data...
[Protection Proxy] Permission check for 'edit'... Granted.
Editing Document: Annual Report
Please enter new content: [用户输入...]
Document saved.
案例三:智能指针 -

应用场景 :C++中原始指针的管理需要开发者手动newdelete,极易导致内存泄漏、悬空指针等问题。智能指针(如std::shared_ptr)是代理模式的经典应用,它代理了原始指针,通过引用计数自动管理生命周期。

std::shared_ptr本身就是一个复杂的库实现,但其核心思想可以用一个简化的版本来说明:

cpp 复制代码
#include <iostream>

// 简化的智能指针代理模板类 (RefCountedSmartProxy)
template<typename T>
class SmartPtr {
public:
    // 构造函数,获取资源
    explicit SmartPtr(T* ptr = nullptr) : m_ptr(ptr), m_count(new size_t(1)) {
        std::cout << "SmartPtr Constructor. Ref count: " << *m_count << std::endl;
    }

    // 拷贝构造函数,共享资源,增加引用计数
    SmartPtr(const SmartPtr<T>& other) : m_ptr(other.m_ptr), m_count(other.m_count) {
        (*m_count)++;
        std::cout << "SmartPtr Copy Constructor. Ref count: " << *m_count << std::endl;
    }

    // 析构函数,减少引用计数,计数为0时释放资源
    ~SmartPtr() {
        (*m_count)--;
        std::cout << "SmartPtr Destructor. Ref count: " << *m_count << std::endl;
        if (*m_count == 0) {
            delete m_ptr;
            delete m_count;
            std::cout << "Resource released." << std::endl;
        }
    }

    // 重载 -> 和 * 运算符,使代理对象用起来像原始指针一样
    T* operator->() const { return m_ptr; }
    T& operator*() const { return *m_ptr; }

    // 获取当前引用计数
    size_t use_count() const { return *m_count; }

private:
    T* m_ptr;           // 代理的原始指针
    size_t* m_count;    // 指向引用计数的指针
};

// 一个简单的资源类
class ExpensiveResource {
public:
    ExpensiveResource() { std::cout << "ExpensiveResource Created." << std::endl; }
    ~ExpensiveResource() { std::cout << "ExpensiveResource Destroyed." << std::endl; }
    void doSomething() { std::cout << "Doing something..." << std::endl; }
};

// 客户端代码
int main() {
    std::cout << "--- Block 1 Start ---" << std::endl;
    {
        // 创建一个智能代理,管理一个新资源
        SmartPtr<ExpensiveResource> ptr1(new ExpensiveResource());
        ptr1->doSomething(); // 通过代理使用资源,如同使用原始指针

        std::cout << "--- Block 2 Start ---" << std::endl;
        {
            // 拷贝构造,共享资源
            SmartPtr<ExpensiveResource> ptr2 = ptr1;
            ptr2->doSomething();
            std::cout << "Use count: " << ptr2.use_count() << std::endl; // 应该是2
        } // ptr2析构,计数减1
        std::cout << "--- Block 2 End ---" << std::endl;

        std::cout << "Use count: " << ptr1.use_count() << std::endl; // 应该是1
        ptr1->doSomething();
    } // ptr1析构,计数减为0,资源被释放
    std::cout << "--- Block 1 End ---" << std::endl;

    return 0;
}

输出结果:

复制代码
--- Block 1 Start ---
ExpensiveResource Created.
SmartPtr Constructor. Ref count: 1
Doing something...
--- Block 2 Start ---
SmartPtr Copy Constructor. Ref count: 2
Doing something...
Use count: 2
SmartPtr Destructor. Ref count: 1
--- Block 2 End ---
Use count: 1
Doing something...
SmartPtr Destructor. Ref count: 0
Resource released.
ExpensiveResource Destroyed.
--- Block 1 End ---

这个简化的SmartPtr代理了ExpensiveResource对象的指针,通过重载->*运算符,使得客户端可以像使用原始指针一样使用它。但其内部实现了引用计数,自动管理内存的释放,完美诠释了智能引用代理的角色。

4. 代码实现与编译运行

我们将以案例一(虚拟代理)的代码为例,提供完整的工程化实现,包括注释、流程图和Makefile。

4.1 带完整注释的代码实现

virtual_proxy.h

cpp 复制代码
/**
 * @file virtual_proxy.h
 * @brief 虚拟代理模式示例:图片延迟加载
 * 
 * 定义了Image接口、RealImage实现类和ProxyImage代理类。
 * 代理类负责控制RealImage对象的创建时机,实现延迟加载。
 */

#ifndef VIRTUAL_PROXY_H
#define VIRTUAL_PROXY_H

#include <iostream>
#include <string>

/**
 * @brief 抽象图像接口 (Subject)
 * 
 * 定义客户端使用的通用图像操作接口,RealImage和ProxyImage都实现此接口。
 */
class Image {
public:
    virtual ~Image() = default;
    
    /**
     * @brief 显示图像
     * 
     * 纯虚函数,具体由子类实现显示逻辑。
     */
    virtual void display() = 0;
};

/**
 * @brief 真实图像类 (RealSubject)
 * 
 * 代表实际的高开销图像对象,在构造时即从磁盘加载图像数据。
 */
class RealImage : public Image {
public:
    /**
     * @brief 构造函数
     * @param filename 图像文件名
     * 
     * 构造RealImage对象并立即调用loadFromDisk加载图像数据。
     */
    explicit RealImage(const std::string& filename);

    /**
     * @brief 显示图像
     * 
     * 将已加载的图像数据渲染到屏幕上(此处模拟输出)。
     */
    void display() override;

private:
    /**
     * @brief 从磁盘加载图像
     * 
     * 模拟一个耗时且资源密集型的磁盘I/O操作。
     * 此函数由构造函数调用,不应由客户端直接调用。
     */
    void loadFromDisk();

    std::string m_filename; ///< 图像文件路径
};

/**
 * @brief 图像代理类 (Proxy)
 * 
 * 作为RealImage的代理,控制对其的访问。
 * 实现延迟加载:仅在第一次调用display()时创建RealImage对象。
 */
class ProxyImage : public Image {
public:
    /**
     * @brief 构造函数
     * @param filename 图像文件名
     * 
     * 仅存储文件名,并不立即创建或加载RealImage对象,启动速度快。
     */
    explicit ProxyImage(const std::string& filename);

    /**
     * @brief 析构函数
     * 
     * 负责释放所管理的RealImage对象(如果已创建)。
     */
    ~ProxyImage();

    /**
     * @brief 显示图像
     * 
     * 首次调用时,会创建并加载RealImage对象,后续调用直接使用已加载的对象。
     */
    void display() override;

private:
    std::string m_filename;   ///< 图像文件路径
    RealImage* m_realImage;   ///< 指向真实图像对象的指针
};

#endif // VIRTUAL_PROXY_H

virtual_proxy.cpp

cpp 复制代码
/**
 * @file virtual_proxy.cpp
 * @brief 虚拟代理模式示例的实现文件
 */

#include "virtual_proxy.h"

// RealImage 实现
RealImage::RealImage(const std::string& filename) : m_filename(filename) {
    loadFromDisk();
}

void RealImage::display() {
    std::cout << "Displaying image: " << m_filename << std::endl;
}

void RealImage::loadFromDisk() {
    std::cout << "Loading heavy image from disk: " << m_filename << " (This operation is expensive!)" << std::endl;
    // 模拟耗时操作,例如:
    // for (int i = 0; i < 1000000000; ++i) {} // 空循环模拟耗时
    // 实际代码中可能是: m_imageData = stbi_load(filename, ...);
}

// ProxyImage 实现
ProxyImage::ProxyImage(const std::string& filename) : m_filename(filename), m_realImage(nullptr) {
    std::cout << "Proxy created for image: " << m_filename << " (RealImage not loaded yet)" << std::endl;
}

ProxyImage::~ProxyImage() {
    delete m_realImage; // Safe to delete nullptr
}

void ProxyImage::display() {
    // 延迟初始化 (Lazy Initialization)
    if (m_realImage == nullptr) {
        std::cout << "Proxy: First time display requested. Creating RealImage..." << std::endl;
        m_realImage = new RealImage(m_filename);
    } else {
        std::cout << "Proxy: RealImage already exists. Reusing it." << std::endl;
    }
    // 委托给真实对象处理请求
    m_realImage->display();
}

main.cpp

cpp 复制代码
/**
 * @file main.cpp
 * @brief 客户端代码,演示虚拟代理的使用
 * 
 * 创建多个图像代理,模拟用户滚动查看图像的场景,展示延迟加载的效果。
 */

#include "virtual_proxy.h"
#include <vector>

int main() {
    std::cout << "Application started. Creating image proxies (very fast)..." << std::endl;
    
    // 创建图像代理列表 - 此时不会加载任何真实图像
    std::vector<ProxyImage> imageProxies;
    imageProxies.emplace_back("vacation_photo_1.jpg");
    imageProxies.emplace_back("vacation_photo_2.jpg");
    imageProxies.emplace_back("vacation_photo_3.jpg");

    std::cout << "\n--- User scrolls to see the first image ---" << std::endl;
    imageProxies[0].display(); // 第一次显示,触发加载

    std::cout << "\n--- User scrolls to see the third image ---" << std::endl;
    imageProxies[2].display(); // 第一次显示,触发加载

    std::cout << "\n--- User scrolls back to the first image ---" << std::endl;
    imageProxies[0].display(); // 再次显示,使用已加载对象

    std::cout << "\nApplication shutdown." << std::endl;
    return 0;
}
4.2 流程图与编译运行

流程图:

下面的流程图描述了代理模式在客户端请求时的决策和执行流程。
否 是 客户端调用 proxy.display RealImage 是否存在? 创建 RealImage 对象 从磁盘加载图像数据 调用 realImage.display 图像显示完成

Makefile 范例:

makefile 复制代码
# Compiler and flags
CXX := g++
CXXFLAGS := -std=c++17 -Wall -Wextra -O2

# Targets
TARGET := virtual_proxy_demo
SRCS := main.cpp virtual_proxy.cpp
OBJS := $(SRCS:.cpp=.o)
HEADERS := virtual_proxy.h

.PHONY: all clean

all: $(TARGET)

$(TARGET): $(OBJS)
	$(CXX) $(CXXFLAGS) -o $@ $^

%.o: %.cpp $(HEADERS)
	$(CXX) $(CXXFLAGS) -c $< -o $@

clean:
	rm -f $(TARGET) $(OBJS)

# 运行目标
run: $(TARGET)
	./$(TARGET)

编译与运行方法:

  1. 保存文件 :将上述代码分别保存为 virtual_proxy.h, virtual_proxy.cpp, main.cppMakefile,放在同一目录下。

  2. 编译项目 :打开终端,导航到该目录,执行 make 命令。

    复制代码
    $ make
    g++ -std=c++17 -Wall -Wextra -O2 -c main.cpp -o main.o
    g++ -std=c++17 -Wall -Wextra -O2 -c virtual_proxy.cpp -o virtual_proxy.o
    g++ -std=c++17 -Wall -Wextra -O2 -o virtual_proxy_demo main.o virtual_proxy.o

    这将生成可执行文件 virtual_proxy_demo

  3. 运行程序 :执行 make run 或直接运行生成的可执行文件。

    复制代码
    $ ./virtual_proxy_demo
  4. 结果解读
    观察输出,你可以清晰地看到:

    • 程序启动时,只创建了代理对象(Proxy created...),速度很快。
    • 当第一次请求显示某张图片时(First time display requested...),代理才去创建并加载真实的RealImage对象(Loading heavy image...)。
    • 再次请求显示同一张图片时,代理会直接使用已创建的对象(RealImage already exists...),避免了重复的昂贵加载操作。
      这完美验证了虚拟代理"延迟加载,优化性能"的设计目标。

5. 交互性内容解析(以远程代理为例)

远程代理是代理模式在处理网络通信时的典型应用。它通常涉及两个部分:

  1. 客户端存根 (Stub) :位于客户端本地,代理远程对象。它负责将本地的方法调用序列化成网络报文(Marshalling),并通过网络发送给服务器。
  2. 服务器骨架 (Skeleton) :位于服务器端,接收客户端请求,反序列化报文(Unmarshalling),调用真实的远程对象方法,再将结果序列化并传回客户端。

其交互时序图如下:
Client Proxy(Stub) Network Skeleton RealObject call method() 1. 序列化参数 2. 组织请求报文 send(request message) request message 3. 反序列化参数 4. 调用真实对象 method(args) result 5. 序列化结果 6. 组织响应报文 send(response message) response message 7. 反序列化结果 return result Client Proxy(Stub) Network Skeleton RealObject

报文结构示例(简化):

一个简单的RPC请求报文可能包含以下信息:

复制代码
| Magic Number (2 bytes) | Version (1 byte) | Message Type (1 byte) | Request ID (4 bytes) | Method Name Length (2 bytes) | Method Name (UTF-8 String) | Payload Length (4 bytes) | Payload (Serialized Parameters) |
  • Magic Number :标识协议,例如 0xCAFE
  • Message Type:区分是请求(0x01)还是响应(0x02)。
  • Request ID:用于匹配请求和响应。
  • Method Name:客户端要调用的远程方法名。
  • Payload:方法的参数,通常使用JSON、Protobuf、MsgPack等格式序列化。

客户端存根(代理)的工作就是填充这个报文并发送,服务器骨架则解析这个报文,找到对应的真实方法并调用。对客户端而言,它就像在调用本地方法一样,完全感知不到网络通信的存在。这就是远程代理实现的"位置透明性"。

总结

代理模式是一种强大而灵活的结构型设计模式,它通过引入一个代理对象来控制对另一个对象的访问。这种控制带来了诸多好处,包括延迟加载、权限控制、简化复杂访问以及智能资源管理等。其核心在于添加一层间接性,这符合许多软件设计原则,使得系统更加清晰、可扩展和可维护。

从简单的虚拟代理图片,到复杂的RPC框架远程代理,再到C++标准库中的智能指针,代理模式的应用几乎无处不在。理解和掌握代理模式,不仅能帮助我们写出更优雅、高效的代码,更能让我们深刻体会到面向对象设计中"间接层"的艺术和威力。当您下一次看到std::shared_ptr或者Hibernate的懒加载时,您会心一笑,因为您知道,这背后正是代理模式在默默地发挥着作用。

相关推荐
小年糕是糕手3 小时前
【C语言】C语言预处理详解,从基础到进阶的全面讲解
linux·c语言·开发语言·数据结构·c++·windows·microsoft
澄澈i3 小时前
CMake学习篇[2]---CMake进阶+非同级目录构建+静态库/动态库链接
c++·学习·cmake
起个名字费劲死了4 小时前
Pytorch Yolov11 OBB 旋转框检测+window部署+推理封装 留贴记录
c++·人工智能·pytorch·python·深度学习·yolo·机器人
高山有多高4 小时前
从 0 到 1 保姆级实现C语言双向链表
c语言·开发语言·数据结构·c++·算法·visual studio
new_daimond4 小时前
设计模式-备忘录模式详解
设计模式·备忘录模式
aluluka4 小时前
Emacs 折腾日记(三十)——打造C++ IDE 续
c++·ide·emacs
半桔4 小时前
【网络编程】UDP 编程实战:从套接字到聊天室多场景项目构建
linux·网络·c++·网络协议·udp
Lucis__4 小时前
C++相关概念与语法基础——C基础上的改进与优化
c语言·开发语言·c++