Skip to content

代理模式详解:概念、实现与应用

引言

代理模式是一种结构型设计模式,它能让你提供对象的替代品或其占位符。代理控制着对于原对象的访问,并允许在将请求提交给对象前后进行一些处理。

什么是代理模式?

代理模式是一种结构型设计模式,它能让你提供对象的替代品或其占位符。代理控制着对于原对象的访问,并允许在将请求提交给对象前后进行一些处理。代理模式通过创建一个代理对象来控制对真实对象的访问。

核心思想

代理模式的核心思想是:

  1. 访问控制:控制对真实对象的访问
  2. 延迟初始化:延迟创建开销大的对象
  3. 增强功能:在访问真实对象前后添加额外功能

为什么需要代理模式?

在许多情况下,我们需要在访问对象时添加额外的控制:

1. 远程代理

当对象位于不同的地址空间或网络中时:

  • 隐藏网络通信的复杂性
  • 提供本地对象接口访问远程对象
  • 处理网络错误和重试

2. 虚拟代理

当对象创建开销很大时:

  • 延迟创建昂贵的对象
  • 在需要时才加载资源
  • 提供占位符对象

3. 保护代理

当需要控制对象的访问权限时:

  • 基于用户角色控制访问
  • 实现细粒度的权限控制
  • 记录访问日志

代理模式的基本实现

让我们从一个简单的代理模式实现开始:

javascript
// 主题接口
class Subject {
  request() {
    throw new Error('必须实现 request 方法');
  }
}

// 真实主题
class RealSubject extends Subject {
  request() {
    return '真实主题:处理请求';
  }
}

// 代理
class Proxy extends Subject {
  constructor(realSubject) {
    super();
    this.realSubject = realSubject;
  }
  
  request() {
    if (this.checkAccess()) {
      const result = this.realSubject.request();
      this.logAccess();
      return result;
    }
    return '代理:访问被拒绝';
  }
  
  checkAccess() {
    console.log('代理:检查访问权限');
    return true;
  }
  
  logAccess() {
    console.log('代理:记录访问日志');
  }
}

// 客户端代码
function clientCode(subject) {
  console.log(subject.request());
}

console.log('客户端:直接执行真实主题:');
const realSubject = new RealSubject();
clientCode(realSubject);

console.log('');

console.log('客户端:通过代理执行:');
const proxy = new Proxy(realSubject);
clientCode(proxy);

实现要点分析

  1. 主题接口:定义真实对象和代理对象的公共接口
  2. 真实主题:实现实际业务逻辑的对象
  3. 代理:实现与真实主题相同的接口,控制对真实主题的访问
  4. 客户端:通过主题接口与对象交互

代理模式的实际应用场景

1. 图像加载代理

在图形应用中,代理模式非常适合用于延迟加载大图像:

javascript
// 图像接口
class Image {
  display() {
    throw new Error('必须实现 display 方法');
  }
}

// 真实图像
class RealImage extends Image {
  constructor(filename) {
    super();
    this.filename = filename;
    this.loadImageFromDisk();
  }
  
  loadImageFromDisk() {
    console.log(`从磁盘加载图像: ${this.filename}`);
    // 模拟耗时的图像加载过程
    console.log('图像加载完成');
  }
  
  display() {
    return `显示图像: ${this.filename}`;
  }
}

// 图像代理
class ImageProxy extends Image {
  constructor(filename) {
    super();
    this.filename = filename;
    this.realImage = null;
  }
  
  display() {
    if (!this.realImage) {
      console.log('图像代理:首次访问,创建真实图像对象');
      this.realImage = new RealImage(this.filename);
    }
    return this.realImage.display();
  }
}

// 图像浏览器
class ImageViewer {
  constructor() {
    this.images = new Map();
  }
  
  loadImage(filename) {
    if (!this.images.has(filename)) {
      console.log(`图像浏览器:注册图像 ${filename}`);
      this.images.set(filename, new ImageProxy(filename));
    }
    return this.images.get(filename);
  }
  
  displayImage(filename) {
    const image = this.loadImage(filename);
    console.log(image.display());
  }
}

// 使用示例
function imageProxyExample() {
  const viewer = new ImageViewer();
  
  console.log('=== 注册图像 ===');
  viewer.loadImage('photo1.jpg');
  viewer.loadImage('photo2.jpg');
  viewer.loadImage('photo3.jpg');
  
  console.log('\n=== 显示图像 ===');
  viewer.displayImage('photo1.jpg');
  console.log('');
  viewer.displayImage('photo2.jpg');
  console.log('');
  viewer.displayImage('photo1.jpg'); // 再次显示,不会重新加载
}

imageProxyExample();

2. API 请求代理

在 Web 应用中,代理模式非常适合用于缓存和控制 API 请求:

javascript
// API 客户端接口
class APIClient {
  async get(url) {
    throw new Error('必须实现 get 方法');
  }
  
  async post(url, data) {
    throw new Error('必须实现 post 方法');
  }
}

// 真实 API 客户端
class RealAPIClient extends APIClient {
  async get(url) {
    console.log(`真实客户端:发送 GET 请求到 ${url}`);
    // 模拟网络延迟
    await new Promise(resolve => setTimeout(resolve, 1000));
    
    // 模拟 API 响应
    return {
      status: 200,
      data: { message: `GET 请求响应 from ${url}`, timestamp: Date.now() }
    };
  }
  
  async post(url, data) {
    console.log(`真实客户端:发送 POST 请求到 ${url},数据: ${JSON.stringify(data)}`);
    // 模拟网络延迟
    await new Promise(resolve => setTimeout(resolve, 1000));
    
    // 模拟 API 响应
    return {
      status: 201,
      data: { message: `POST 请求响应 from ${url}`, timestamp: Date.now() }
    };
  }
}

// API 代理
class APIProxy extends APIClient {
  constructor(realClient, cache = new Map()) {
    super();
    this.realClient = realClient;
    this.cache = cache;
    this.requestCount = 0;
  }
  
  async get(url) {
    this.requestCount++;
    console.log(`API 代理:处理第 ${this.requestCount} 个请求`);
    
    // 检查缓存
    if (this.cache.has(url)) {
      console.log(`API 代理:从缓存返回数据 for ${url}`);
      return this.cache.get(url);
    }
    
    // 检查访问频率
    if (!this.checkRateLimit()) {
      throw new Error('API 代理:请求频率过高');
    }
    
    // 检查权限
    if (!this.checkPermission('read')) {
      throw new Error('API 代理:读取权限不足');
    }
    
    console.log(`API 代理:转发请求到真实客户端`);
    const response = await this.realClient.get(url);
    
    // 缓存响应(仅对 GET 请求)
    this.cache.set(url, response);
    
    // 记录日志
    this.logRequest('GET', url);
    
    return response;
  }
  
  async post(url, data) {
    this.requestCount++;
    console.log(`API 代理:处理第 ${this.requestCount} 个请求`);
    
    // 检查访问频率
    if (!this.checkRateLimit()) {
      throw new Error('API 代理:请求频率过高');
    }
    
    // 检查权限
    if (!this.checkPermission('write')) {
      throw new Error('API 代理:写入权限不足');
    }
    
    console.log(`API 代理:转发请求到真实客户端`);
    const response = await this.realClient.post(url, data);
    
    // 清除相关缓存
    this.clearCacheForUrl(url);
    
    // 记录日志
    this.logRequest('POST', url, data);
    
    return response;
  }
  
  checkRateLimit() {
    // 简单的频率限制:每秒最多 5 个请求
    if (this.requestCount > 5) {
      console.log('API 代理:请求频率过高');
      return false;
    }
    return true;
  }
  
  checkPermission(operation) {
    // 简单的权限检查
    const permissions = ['read', 'write'];
    return permissions.includes(operation);
  }
  
  logRequest(method, url, data = null) {
    console.log(`API 代理:记录请求日志 - ${method} ${url}${data ? ` with data: ${JSON.stringify(data)}` : ''}`);
  }
  
  clearCacheForUrl(url) {
    // 清除与该 URL 相关的缓存
    for (const key of this.cache.keys()) {
      if (key.includes(url)) {
        this.cache.delete(key);
      }
    }
  }
  
  getCacheStats() {
    return {
      cacheSize: this.cache.size,
      requestCount: this.requestCount
    };
  }
}

// API 管理器
class APIManager {
  constructor() {
    this.clients = new Map();
    this.cache = new Map();
  }
  
  registerClient(name, client) {
    const proxy = new APIProxy(client, this.cache);
    this.clients.set(name, proxy);
  }
  
  getClient(name) {
    const client = this.clients.get(name);
    if (!client) {
      throw new Error(`未找到 API 客户端: ${name}`);
    }
    return client;
  }
  
  async request(clientName, method, url, data = null) {
    const client = this.getClient(clientName);
    
    try {
      let response;
      switch (method.toLowerCase()) {
        case 'get':
          response = await client.get(url);
          break;
        case 'post':
          response = await client.post(url, data);
          break;
        default:
          throw new Error(`不支持的 HTTP 方法: ${method}`);
      }
      
      console.log(`API 请求成功: ${method} ${url}`);
      return response;
    } catch (error) {
      console.error(`API 请求失败: ${method} ${url}`, error.message);
      throw error;
    }
  }
  
  getStats(clientName) {
    const client = this.getClient(clientName);
    return client.getCacheStats();
  }
}

// 使用示例
async function apiProxyExample() {
  const apiManager = new APIManager();
  
  // 注册 API 客户端
  apiManager.registerClient('main', new RealAPIClient());
  
  console.log('=== 发送 GET 请求 ===');
  try {
    const response1 = await apiManager.request('main', 'GET', '/api/users');
    console.log('响应:', response1);
    
    // 再次请求相同 URL,应该从缓存获取
    const response2 = await apiManager.request('main', 'GET', '/api/users');
    console.log('缓存响应:', response2);
  } catch (error) {
    console.error('GET 请求失败:', error.message);
  }
  
  console.log('\n=== 发送 POST 请求 ===');
  try {
    const response = await apiManager.request('main', 'POST', '/api/users', { name: '张三', email: 'zhangsan@example.com' });
    console.log('响应:', response);
  } catch (error) {
    console.error('POST 请求失败:', error.message);
  }
  
  console.log('\n=== 获取统计信息 ===');
  const stats = apiManager.getStats('main');
  console.log('统计信息:', stats);
}

// 运行示例
apiProxyExample();

3. 数据库连接代理

在数据库访问中,代理模式非常适合用于连接池管理和权限控制:

javascript
// 数据库连接接口
class DatabaseConnection {
  query(sql) {
    throw new Error('必须实现 query 方法');
  }
  
  execute(sql) {
    throw new Error('必须实现 execute 方法');
  }
  
  close() {
    throw new Error('必须实现 close 方法');
  }
}

// 真实数据库连接
class RealDatabaseConnection extends DatabaseConnection {
  constructor(connectionId) {
    super();
    this.connectionId = connectionId;
    this.isConnected = true;
    console.log(`真实连接:创建数据库连接 ${this.connectionId}`);
  }
  
  query(sql) {
    if (!this.isConnected) {
      throw new Error('连接已关闭');
    }
    
    console.log(`真实连接 ${this.connectionId}:执行查询 "${sql}"`);
    // 模拟查询结果
    return {
      rows: [
        { id: 1, name: '用户1', email: 'user1@example.com' },
        { id: 2, name: '用户2', email: 'user2@example.com' }
      ],
      rowCount: 2
    };
  }
  
  execute(sql) {
    if (!this.isConnected) {
      throw new Error('连接已关闭');
    }
    
    console.log(`真实连接 ${this.connectionId}:执行更新 "${sql}"`);
    // 模拟执行结果
    return { affectedRows: 1 };
  }
  
  close() {
    if (this.isConnected) {
      console.log(`真实连接 ${this.connectionId}:关闭数据库连接`);
      this.isConnected = false;
    }
  }
}

// 数据库连接代理
class DatabaseConnectionProxy extends DatabaseConnection {
  constructor(realConnection, userRole) {
    super();
    this.realConnection = realConnection;
    this.userRole = userRole;
    this.queryCount = 0;
    this.executeCount = 0;
  }
  
  query(sql) {
    this.queryCount++;
    
    // 检查权限
    if (!this.checkQueryPermission(sql)) {
      throw new Error('查询权限不足');
    }
    
    // 检查查询频率
    if (!this.checkRateLimit()) {
      throw new Error('查询频率过高');
    }
    
    // 记录查询日志
    this.logQuery(sql);
    
    // 执行真实查询
    return this.realConnection.query(sql);
  }
  
  execute(sql) {
    this.executeCount++;
    
    // 检查权限
    if (!this.checkExecutePermission(sql)) {
      throw new Error('执行权限不足');
    }
    
    // 检查执行频率
    if (!this.checkRateLimit()) {
      throw new Error('执行频率过高');
    }
    
    // 记录执行日志
    this.logExecute(sql);
    
    // 执行真实操作
    return this.realConnection.execute(sql);
  }
  
  close() {
    console.log(`连接代理:关闭连接,查询次数: ${this.queryCount}, 执行次数: ${this.executeCount}`);
    this.realConnection.close();
  }
  
  checkQueryPermission(sql) {
    const lowerSql = sql.toLowerCase();
    
    // 管理员可以执行所有查询
    if (this.userRole === 'admin') {
      return true;
    }
    
    // 普通用户只能执行 SELECT 查询
    if (this.userRole === 'user' && lowerSql.trim().startsWith('select')) {
      return true;
    }
    
    return false;
  }
  
  checkExecutePermission(sql) {
    const lowerSql = sql.toLowerCase();
    
    // 只有管理员可以执行修改操作
    if (this.userRole === 'admin') {
      return true;
    }
    
    // 普通用户不能执行修改操作
    if (this.userRole === 'user') {
      return false;
    }
    
    return false;
  }
  
  checkRateLimit() {
    const totalOperations = this.queryCount + this.executeCount;
    
    // 简单的频率限制:每个连接最多 100 次操作
    if (totalOperations > 100) {
      console.log('连接代理:操作频率过高');
      return false;
    }
    
    return true;
  }
  
  logQuery(sql) {
    console.log(`连接代理:记录查询日志 - 用户: ${this.userRole}, SQL: ${sql}`);
  }
  
  logExecute(sql) {
    console.log(`连接代理:记录执行日志 - 用户: ${this.userRole}, SQL: ${sql}`);
  }
  
  getStats() {
    return {
      queryCount: this.queryCount,
      executeCount: this.executeCount,
      userRole: this.userRole
    };
  }
}

// 数据库连接池
class DatabaseConnectionPool {
  constructor() {
    this.connections = new Map();
    this.connectionCounter = 0;
  }
  
  getConnection(userRole = 'user') {
    this.connectionCounter++;
    const connectionId = `conn_${this.connectionCounter}`;
    
    const realConnection = new RealDatabaseConnection(connectionId);
    const proxyConnection = new DatabaseConnectionProxy(realConnection, userRole);
    
    this.connections.set(connectionId, proxyConnection);
    return proxyConnection;
  }
  
  releaseConnection(connection) {
    // 在实际实现中,这里会将连接返回到池中
    // 为了简化,我们直接关闭连接
    connection.close();
  }
  
  getStats() {
    const stats = {};
    for (const [id, connection] of this.connections) {
      stats[id] = connection.getStats();
    }
    return stats;
  }
}

// 使用示例
function databaseProxyExample() {
  const connectionPool = new DatabaseConnectionPool();
  
  console.log('=== 普通用户连接 ===');
  const userConnection = connectionPool.getConnection('user');
  
  try {
    // 普通用户可以执行查询
    const users = userConnection.query('SELECT * FROM users');
    console.log('查询结果:', users);
    
    // 普通用户不能执行更新操作
    try {
      userConnection.execute('UPDATE users SET name = "新名字" WHERE id = 1');
    } catch (error) {
      console.error('执行失败:', error.message);
    }
  } catch (error) {
    console.error('操作失败:', error.message);
  }
  
  console.log('\n=== 管理员连接 ===');
  const adminConnection = connectionPool.getConnection('admin');
  
  try {
    // 管理员可以执行查询
    const users = adminConnection.query('SELECT * FROM users');
    console.log('查询结果:', users);
    
    // 管理员可以执行更新操作
    const result = adminConnection.execute('UPDATE users SET name = "新名字" WHERE id = 1');
    console.log('更新结果:', result);
  } catch (error) {
    console.error('操作失败:', error.message);
  }
  
  console.log('\n=== 释放连接 ===');
  connectionPool.releaseConnection(userConnection);
  connectionPool.releaseConnection(adminConnection);
  
  console.log('\n=== 连接池统计 ===');
  const stats = connectionPool.getStats();
  console.log('统计信息:', JSON.stringify(stats, null, 2));
}

databaseProxyExample();

代理模式的三种主要类型

1. 远程代理

远程代理为不同地址空间中的对象提供本地代表:

javascript
// 远程服务接口
class RemoteService {
  async processData(data) {
    throw new Error('必须实现 processData 方法');
  }
}

// 远程服务实现
class RealRemoteService extends RemoteService {
  async processData(data) {
    console.log('真实远程服务:处理数据');
    // 模拟网络延迟
    await new Promise(resolve => setTimeout(resolve, 2000));
    return { result: `处理完成: ${data}`, timestamp: Date.now() };
  }
}

// 远程代理
class RemoteProxy extends RemoteService {
  constructor(remoteServiceUrl) {
    super();
    this.remoteServiceUrl = remoteServiceUrl;
    this.realService = null;
  }
  
  async processData(data) {
    if (!this.realService) {
      console.log('远程代理:初始化远程服务连接');
      this.realService = new RealRemoteService();
    }
    
    try {
      console.log('远程代理:转发请求到远程服务');
      const result = await this.realService.processData(data);
      return result;
    } catch (error) {
      console.error('远程代理:远程服务调用失败', error.message);
      throw new Error(`远程服务错误: ${error.message}`);
    }
  }
}

2. 虚拟代理

虚拟代理根据需要创建开销大的对象:

javascript
// 图像接口
class Image {
  display() {
    throw new Error('必须实现 display 方法');
  }
}

// 大图像实现
class HeavyImage extends Image {
  constructor(filename) {
    super();
    this.filename = filename;
    this.loadImage(); // 昂贵的操作
  }
  
  loadImage() {
    console.log(`加载大图像: ${this.filename}`);
    // 模拟昂贵的加载过程
    console.log('图像加载完成');
  }
  
  display() {
    return `显示大图像: ${this.filename}`;
  }
}

// 虚拟代理
class VirtualImageProxy extends Image {
  constructor(filename) {
    super();
    this.filename = filename;
    this.realImage = null;
  }
  
  display() {
    if (!this.realImage) {
      console.log('虚拟代理:按需创建真实图像');
      this.realImage = new HeavyImage(this.filename);
    }
    return this.realImage.display();
  }
}

3. 保护代理

保护代理控制对敏感对象的访问:

javascript
// 文档接口
class Document {
  read() {
    throw new Error('必须实现 read 方法');
  }
  
  write(content) {
    throw new Error('必须实现 write 方法');
  }
}

// 真实文档
class RealDocument extends Document {
  constructor(title, content) {
    super();
    this.title = title;
    this.content = content;
  }
  
  read() {
    return this.content;
  }
  
  write(content) {
    this.content = content;
    return '文档已更新';
  }
}

// 保护代理
class ProtectionProxy extends Document {
  constructor(realDocument, userRole) {
    super();
    this.realDocument = realDocument;
    this.userRole = userRole;
  }
  
  read() {
    console.log(`保护代理:用户 ${this.userRole} 尝试读取文档`);
    return this.realDocument.read();
  }
  
  write(content) {
    if (this.userRole !== 'admin') {
      throw new Error('写入权限不足');
    }
    
    console.log(`保护代理:管理员 ${this.userRole} 尝试写入文档`);
    return this.realDocument.write(content);
  }
}

代理模式与其它模式的对比

代理模式 vs 装饰器模式

javascript
// 代理模式 - 控制访问
class Proxy {
  request() {
    if (this.checkAccess()) {
      return this.realSubject.request();
    }
    return '访问被拒绝';
  }
}

// 装饰器模式 - 增强功能
class Decorator {
  operation() {
    // 增强原有功能
    console.log('添加额外功能');
    return this.component.operation();
  }
}

代理模式 vs 适配器模式

javascript
// 代理模式 - 控制对相同接口的访问
class Proxy {
  request() {
    // 控制访问
    return this.realSubject.request();
  }
}

// 适配器模式 - 转换不同接口
class Adapter {
  request() {
    // 转换接口
    return this.adaptee.specificRequest();
  }
}

代理模式的优缺点

优点

  1. 访问控制:可以控制对服务对象的访问
  2. 延迟初始化:可以延迟创建开销大的对象
  3. 增强功能:可以在访问对象前后添加额外功能
  4. 透明性:客户端无需修改代码即可使用代理
  5. 可复用性:代理可以在不同对象间复用

缺点

  1. 复杂性增加:引入代理会增加代码复杂性
  2. 性能开销:代理可能带来额外的性能开销
  3. 响应时间:可能增加服务响应时间
  4. 调试困难:多层代理可能使调试变得困难

总结

代理模式是一种结构型设计模式,它能让你提供对象的替代品或其占位符。代理控制着对于原对象的访问,并允许在将请求提交给对象前后进行一些处理。

通过本章的学习,我们了解了:

  1. 代理模式的基本概念和核心思想
  2. 代理模式的三种主要类型(远程代理、虚拟代理、保护代理)
  3. 代理模式在实际开发中的应用场景(图像加载、API请求、数据库连接)
  4. 代理模式与其他结构型模式的对比
  5. 代理模式的优缺点

代理模式在现代软件开发中应用广泛,特别是在需要控制对象访问、延迟初始化或增强功能的场景中,它可以很好地支持系统的安全性和性能。

在下一章中,我们将继续探讨其他结构型模式,首先是外观模式。