(9)代理模式

代理模式(Proxy Pattern) 是一种结构型设计模式,它为某个对象提供一个代理(替身) 来控制对该对象的访问。
核心思想:用代理控制对真实对象的访问

角色说明:
 - Subject:定义接口,真实对象和代理都实现同一接口
 - RealSubject:真实对象,执行实际工作
 - Proxy:代理对象,持有真实对象的引用,控制访问

使用场景:
 - 延迟加载(Lazy Loading)
 - 权限控制
 - 日志记录
 - 缓存
 - 远程调用(如网络服务代理)
业务场景:企业文档管理系统
需求背景
公司内部有一个文档系统,用户可以查看文档,但:
 - 某些文档非常大,加载耗时(如PDF、设计图)
 - 只有特定角色(如“管理员”、“经理”)才能访问敏感文档
 - 频繁访问的文档应缓存以提升性能
 - 所有访问行为需要记录日志

使用代理模式解决使用 代理(DocumentProxy) 来控制对真实文档(RealDocument)的访问,实现:
 - 延迟加载(Lazy Load)
 - 权限控制(Access Control)
 - 访问日志(Logging)
 - 缓存机制(Caching)
// ==================== 用户角色 ====================
enum class UserRole {
    GUEST,
    EMPLOYEE,
    MANAGER,
    ADMIN
};

// 将角色转换为字符串(用于日志)
string roleToString(UserRole role) {
    switch (role) {
        case UserRole::GUEST:    return "访客";
        case UserRole::EMPLOYEE: return "员工";
        case UserRole::MANAGER:  return "经理";
        case UserRole::ADMIN:    return "管理员";
        default: return "未知";
    }
}

// 全局缓存(实际中可用 LRU 缓存)
static unordered_map<string, unique_ptr<RealDocument>> documentCache;
// ==================== 文档接口 ====================
class Document {
public:
    virtual ~Document() = default;
    virtual void display(const UserRole& userRole) = 0;
    virtual string getName() const = 0;
};

// ==================== 真实文档:大文件,加载耗时 ====================
class RealDocument : public Document {
private:
    string filename;

    // 模拟耗时加载(如读取大文件)
    void loadFromDisk() {
        std::cout << "正在加载大型文档 '" << filename << "'... ";
        // 模拟耗时操作
        for (volatile int i = 0; i < 5000000; ++i) {}
        std::cout << "加载完成\n";
    }

public:
    explicit RealDocument(const string& file) : filename(file) {
        loadFromDisk();
    }

    void display(const UserRole& userRole) override {
        std::time_t now = std::time(nullptr);
        char* timeStr = std::ctime(&now);
        timeStr[strlen(timeStr)-1] = '\0'; // 去掉换行

        std::cout << "[" << timeStr << "] "
                  << "正在查看文档内容:《" << filename 
                  << "》—— 权限:" << roleToString(userRole) << "\n";
        // 这里可以显示文档内容
    }

    string getName() const override {
        return filename;
    }
};
缓存代理类:DocumentProxy(核心)
class DocumentProxy : public Document {
private:
    string filename;
    mutable unique_ptr<RealDocument> realDocument; // 延迟创建

    // 检查用户是否有权限访问
    bool hasAccess(const UserRole& role) const {
        // 敏感文档只允许经理和管理员
        if (filename.find("confidential") != string::npos ||
            filename.find("salary") != string::npos) {
            return role == UserRole::MANAGER || role == UserRole::ADMIN;
        }
        // 普通文档员工及以上可看
        return role != UserRole::GUEST;
    }

    // 生成日志
    void logAccess(const UserRole& role, bool allowed) const {
        std::time_t now = std::time(nullptr);
        char* timeStr = std::ctime(&now);
        timeStr[strlen(timeStr)-1] = '\0';

        std::cout << "[" << timeStr << "] "
                  << "用户[" << roleToString(role) 
                  << "] 尝试访问 《" << filename 
                  << "》 -> " << (allowed ? "允许" : "拒绝") << "\n";
    }

public:
    explicit DocumentProxy(const string& file) : filename(file), realDocument(nullptr) {}

    void display(const UserRole& userRole) override {
        // 1. 记录访问尝试
        logAccess(userRole, hasAccess(userRole));

        // 2. 权限检查
        if (!hasAccess(userRole)) {
            std::cout << "权限不足,无法查看文档:《" << filename << "》\n\n";
            return;
        }

        // 3. 延迟加载 + 缓存检查
        if (!realDocument) {
            // 检查缓存
            auto it = documentCache.find(filename);
            if (it != documentCache.end()) {
                std::cout << "🔁 从缓存加载文档:《" << filename << "》\n";
                realDocument = std::move(it->second);
                documentCache.erase(it);
            } else {
                std::cout << "🆕 创建新文档对象:《" << filename << "》\n";
                realDocument = std::make_unique<RealDocument>(filename);
            }
        }

        // 4. 显示内容
        realDocument->display(userRole);
        std::cout << "\n";
    }

    string getName() const override {
        return filename;
    }
};
模拟不同用户访问
int main() {
    std::cout << "欢迎使用企业文档管理系统\n\n";

    // 创建多个代理(不立即加载真实文档)
    auto doc1 = std::make_unique<DocumentProxy>("员工手册.pdf");
    auto doc2 = std::make_unique<DocumentProxy>("confidential_projectX.pdf");
    auto doc3 = std::make_unique<DocumentProxy>("salary_q3_2025.xlsx");

    // 不同用户尝试访问
    UserRole guest     = UserRole::GUEST;
    UserRole employee  = UserRole::EMPLOYEE;
    UserRole manager   = UserRole::MANAGER;

    std::cout << "--- 访问测试开始 ---\n\n";

    // 1. 访客尝试访问
    std::cout << "[用户:访客]\n";
    doc1->display(guest);  // 拒绝
    doc2->display(guest);  // 拒绝

    // 2. 员工访问
    std::cout << "[用户:员工]\n";
    doc1->display(employee);  // 允许
    doc2->display(employee);  // 拒绝(机密)
    doc3->display(employee);  // 拒绝(薪资)

    // 3. 经理访问
    std::cout << "[用户:经理]\n";
    doc1->display(manager);   // 允许
    doc2->display(manager);   // 允许
    doc3->display(manager);   // 允许

    // 4. 再次访问(缓存命中)
    std::cout << "[用户:经理] 再次访问员工手册\n";
    doc1->display(manager);  // 应从缓存加载

    return 0;
}
posted @ 2018-12-12 23:43  osbreak  阅读(123)  评论(0)    收藏  举报