如何用代理模式实现查询优化?

摘要:什么是代理模式? 代理模式是一种结构型设计模式,它允许你提供一个替代物或占位符来控制对另一个对象的访问。代理对象充当原始对象的接口,客户端通过代理来间接访问原始对象,从而可以在不改变原始对象代码的情况下添加额外的功能。 代理模式特别有用,因
什么是代理模式? 代理模式是一种结构型设计模式,它允许你提供一个替代物或占位符来控制对另一个对象的访问。代理对象充当原始对象的接口,客户端通过代理来间接访问原始对象,从而可以在不改变原始对象代码的情况下添加额外的功能。 代理模式特别有用,因为我们可以利用接口和类型系统来确保代理和真实对象具有相同的接口。 代理模式(Proxy Pattern) 的核心思想是:不直接访问目标对象,而是通过一个“代理对象(Proxy)”来间接访问。代理对象可以在调用目标对象的前后,执行额外的逻辑。 服务接口 (Service Interface) 声明了服务接口。 代理必须遵循该接口才能伪装成服务对象。 服务 (Service) 类提供了一些实用的业务逻辑。 代理 (Proxy) 类包含一个指向服务对象的引用成员变量。 代理完成其任务 (例如延迟初始化、 记录日志、 访问控制和缓存等) 后会将请求传递给服务对象。 客户端 (Client) 能通过同一接口与服务或代理进行交互, 所以你可在一切需要服务对象的代码中使用代理。 为什么需要代理模式? 在实际项目开发中,我们经常会遇到这样的需求: 你需要在图片加载时显示一个加载动画 你想要延迟创建开销很大的对象 你需要控制对敏感对象的访问权限 你想在某个对象被访问、调用之前或之后,增加一层控制逻辑(比如日志记录、权限验证、缓存、懒加载、远程调用等) 这类场景的典型解决方案,就是代理模式(Proxy Pattern) 代理模式的类型 1. 虚拟代理(延迟加载) 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. 保护代理(访问控制) 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. 日志代理 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 请求代理,包含缓存和重试机制: 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)); } } 使用示例 // 虚拟代理使用 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 使用代理模式来实现对目标对象的增强(如日志、事务、权限控制等)。 @Service public class UserService { @Transactional public void createUser(String name) { // 数据库操作 } } Spring 会为 UserService 创建一个代理对象(JDK 动态代理或 CGLIB 代理)。 当你调用 userService.createUser(...) 时,实际调用的是代理对象的方法。 代理对象在方法执行前后插入事务管理逻辑(如开启事务、提交、回滚等)。 代理模式的优缺点 优点: 开闭原则:可以在不修改真实对象的情况下扩展功能 职责清晰:代理对象专注于控制访问,真实对象专注于业务逻辑 延迟加载:可以优化性能,只在需要时创建昂贵对象 访问控制:可以轻松添加权限检查 缺点: 复杂度增加:引入了额外的抽象层 响应延迟:代理调用可能比直接调用稍慢 可能过度设计:简单场景下可能不需要代理模式 何时使用代理模式? 需要延迟创建开销很大的对象时 需要控制对原始对象的访问权限时 需要在对象访问前后添加额外逻辑时 需要为远程对象提供本地代表时(远程代理) 需要为消耗大量内存的对象提供轻量级代表时 总结 代理模式是一种在不修改原有对象的前提下,通过中间层实现访问控制与逻辑增强的模式。