代理模式(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;
}