常用设计模式:代理模式

什么是代理模式?

代理模式是一种结构型设计模式,它允许你提供一个替代物或占位符来控制对另一个对象的访问。代理对象充当原始对象的接口,客户端通过代理来间接访问原始对象,从而可以在不改变原始对象代码的情况下添加额外的功能。

代理模式特别有用,因为我们可以利用接口和类型系统来确保代理和真实对象具有相同的接口。

代理模式(Proxy Pattern) 的核心思想是:不直接访问目标对象,而是通过一个"代理对象(Proxy)"来间接访问。代理对象可以在调用目标对象的前后,执行额外的逻辑。

  1. 服务接口 (Service Interface) 声明了服务接口。 代理必须遵循该接口才能伪装成服务对象。
  2. 服务 (Service) 类提供了一些实用的业务逻辑。
  3. 代理 (Proxy) 类包含一个指向服务对象的引用成员变量。 代理完成其任务 (例如延迟初始化、 记录日志、 访问控制和缓存等) 后会将请求传递给服务对象。
  4. 客户端 (Client) 能通过同一接口与服务或代理进行交互, 所以你可在一切需要服务对象的代码中使用代理。

为什么需要代理模式?

在实际项目开发中,我们经常会遇到这样的需求:

  • 你需要在图片加载时显示一个加载动画
  • 你想要延迟创建开销很大的对象
  • 你需要控制对敏感对象的访问权限
  • 你想在某个对象被访问、调用之前或之后,增加一层控制逻辑(比如日志记录、权限验证、缓存、懒加载、远程调用等)

这类场景的典型解决方案,就是代理模式(Proxy Pattern)

代理模式的类型

1. 虚拟代理(延迟加载)

typescript 复制代码
interface Image {
  display(): void;
}

class RealImage implements Image {
  private filename: string;

  constructor(filename: string) {
    this.filename = filename;
    this.loadFromDisk();
  }

  private loadFromDisk(): void {
    console.log(`Loading ${this.filename} from disk...`);
  }

  display(): void {
    console.log(`Displaying ${this.filename}`);
  }
}

class ImageProxy implements Image {
  private realImage: RealImage | null = null;
  private filename: string;

  constructor(filename: string) {
    this.filename = filename;
  }

  display(): void {
    if (this.realImage === null) {
      this.realImage = new RealImage(this.filename);
    }
    this.realImage.display();
  }
}

2. 保护代理(访问控制)

typescript 复制代码
interface Database {
  query(sql: string): any[];
}

class RealDatabase implements Database {
  query(sql: string): any[] {
    console.log(`Executing: ${sql}`);
    return [{ result: 'data' }];
  }
}

class ProtectedDatabaseProxy implements Database {
  private realDatabase: RealDatabase;
  private userRole: string;

  constructor(userRole: string) {
    this.realDatabase = new RealDatabase();
    this.userRole = userRole;
  }

  query(sql: string): any[] {
    if (this.userRole !== 'admin' && sql.toLowerCase().includes('delete')) {
      throw new Error('Permission denied: Only admins can execute DELETE queries');
    }
    return this.realDatabase.query(sql);
  }
}

3. 日志代理

typescript 复制代码
interface Calculator {
  add(a: number, b: number): number;
  multiply(a: number, b: number): number;
}

class SimpleCalculator implements Calculator {
  add(a: number, b: number): number {
    return a + b;
  }

  multiply(a: number, b: number): number {
    return a * b;
  }
}

class LoggingCalculatorProxy implements Calculator {
  private calculator: Calculator;

  constructor(calculator: Calculator) {
    this.calculator = calculator;
  }

  add(a: number, b: number): number {
    console.log(`Calling add with ${a} and ${b}`);
    const result = this.calculator.add(a, b);
    console.log(`Result: ${result}`);
    return result;
  }

  multiply(a: number, b: number): number {
    console.log(`Calling multiply with ${a} and ${b}`);
    const result = this.calculator.multiply(a, b);
    console.log(`Result: ${result}`);
    return result;
  }
}

实际应用示例:API 请求代理

让我们看一个更实际的例子------创建一个 API 请求代理,包含缓存和重试机制:

typescript 复制代码
interface ApiClient {
  get(url: string): Promise<any>;
  post(url: string, data: any): Promise<any>;
}

class RealApiClient implements ApiClient {
  async get(url: string): Promise<any> {
    const response = await fetch(url);
    return response.json();
  }

  async post(url: string, data: any): Promise<any> {
    const response = await fetch(url, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data)
    });
    return response.json();
  }
}

class SmartApiProxy implements ApiClient {
  private realClient: ApiClient;
  private cache: Map<string, { data: any; timestamp: number }> = new Map();
  private readonly CACHE_DURATION = 5 * 60 * 1000; // 5分钟

  constructor(realClient: ApiClient) {
    this.realClient = realClient;
  }

  async get(url: string): Promise<any> {
    const cached = this.cache.get(url);
    
    // 检查缓存是否有效
    if (cached && Date.now() - cached.timestamp < this.CACHE_DURATION) {
      console.log(`Returning cached data for ${url}`);
      return cached.data;
    }

    // 实现重试机制
    let lastError: Error;
    for (let attempt = 1; attempt <= 3; attempt++) {
      try {
        console.log(`Attempt ${attempt} for ${url}`);
        const data = await this.realClient.get(url);
        
        // 更新缓存
        this.cache.set(url, { data, timestamp: Date.now() });
        return data;
      } catch (error) {
        lastError = error as Error;
        if (attempt < 3) {
          await this.delay(1000 * attempt); // 指数退避
        }
      }
    }
    
    throw lastError!;
  }

  async post(url: string, data: any): Promise<any> {
    // POST 请求不缓存,但实现重试
    let lastError: Error;
    for (let attempt = 1; attempt <= 3; attempt++) {
      try {
        const result = await this.realClient.post(url, data);
        return result;
      } catch (error) {
        lastError = error as Error;
        if (attempt < 3) {
          await this.delay(1000 * attempt);
        }
      }
    }
    throw lastError!;
  }

  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

使用示例

typescript 复制代码
// 虚拟代理使用
const imageProxy = new ImageProxy('photo.jpg');
// 此时图片尚未加载
imageProxy.display(); // 第一次调用时加载并显示

// 保护代理使用
const userDatabase = new ProtectedDatabaseProxy('user');
const adminDatabase = new ProtectedDatabaseProxy('admin');

userDatabase.query('SELECT * FROM users'); // 正常执行
// userDatabase.query('DELETE FROM users'); // 抛出错误

// 智能API代理使用
const apiClient = new SmartApiProxy(new RealApiClient());

async function example() {
  // 第一次请求,从网络获取并缓存
  const data1 = await apiClient.get('https://api.example.com/data');
  
  // 第二次请求,返回缓存数据
  const data2 = await apiClient.get('https://api.example.com/data');
}

真实案例

Spring AOP

Spring AOP 使用代理模式来实现对目标对象的增强(如日志、事务、权限控制等)。

java 复制代码
@Service
public class UserService {
    @Transactional
    public void createUser(String name) {
        // 数据库操作
    }
}
  • Spring 会为 UserService 创建一个代理对象(JDK 动态代理或 CGLIB 代理)。
  • 当你调用 userService.createUser(...) 时,实际调用的是代理对象的方法。
  • 代理对象在方法执行前后插入事务管理逻辑(如开启事务、提交、回滚等)。

代理模式的优缺点

优点:

  • 开闭原则:可以在不修改真实对象的情况下扩展功能
  • 职责清晰:代理对象专注于控制访问,真实对象专注于业务逻辑
  • 延迟加载:可以优化性能,只在需要时创建昂贵对象
  • 访问控制:可以轻松添加权限检查

缺点:

  • 复杂度增加:引入了额外的抽象层
  • 响应延迟:代理调用可能比直接调用稍慢
  • 可能过度设计:简单场景下可能不需要代理模式

何时使用代理模式?

  • 需要延迟创建开销很大的对象时
  • 需要控制对原始对象的访问权限时
  • 需要在对象访问前后添加额外逻辑时
  • 需要为远程对象提供本地代表时(远程代理)
  • 需要为消耗大量内存的对象提供轻量级代表时

总结

代理模式是一种在不修改原有对象的前提下,通过中间层实现访问控制与逻辑增强的模式。

相关推荐
西幻凌云3 小时前
认识设计模式——单例模式
c++·单例模式·设计模式·线程安全·饿汉和懒汉
爱吃烤鸡翅的酸菜鱼4 小时前
【Java】基于策略模式 + 工厂模式多设计模式下:重构租房系统核心之城市房源列表缓存与高性能筛选
java·redis·后端·缓存·设计模式·重构·策略模式
在未来等你13 小时前
AI Agent设计模式 Day 5:Reflexion模式:自我反思与持续改进
设计模式·llm·react·ai agent·plan-and-execute
程序员三藏13 小时前
快速弄懂POM设计模式
自动化测试·软件测试·python·selenium·测试工具·设计模式·职场和发展
Lei_33596713 小时前
[設計模式]設計模式的作用
设计模式
将编程培养成爱好15 小时前
C++ 设计模式《统计辅助功能》
开发语言·c++·设计模式·访问者模式
乙己40719 小时前
设计模式——桥接模式(bridge)
设计模式·桥接模式
WKP94181 天前
原型设计模式
java·设计模式
guangzan1 天前
常用设计模式:工厂方法模式
设计模式