代理模式 (Proxy Pattern)
NOTE
代理模式为其他对象提供一种代理以控制对这个对象的访问,在访问对象时引入一定程度的间接性。
📖 模式定义
代理模式是一种结构型设计模式,它为另一个对象提供一个替身或占位符以控制对它的访问。代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能。
核心要素
- 主题接口:定义代理和真实主题的公共接口
- 真实主题:定义代理所代表的真实对象
- 代理:保存一个引用使得代理可以访问实体,控制对真实主题的访问
- 客户端:通过代理接口与真实主题交互
🎯 使用场景
适用情况
- 远程代理:为一个对象在不同的地址空间提供局部代表
- 虚拟代理:根据需要创建开销很大的对象
- 保护代理:控制对原始对象的访问权限
- 智能引用:在访问对象时执行一些附加操作
💡 实现方式
TypeScript 实现
typescript
// 主题接口
interface Image {
display(): void;
getSize(): number;
}
// 真实主题 - 真实图片
class RealImage implements Image {
private filename: string;
private size: number;
constructor(filename: string) {
this.filename = filename;
this.size = Math.floor(Math.random() * 1000) + 100; // 模拟文件大小
this.loadFromDisk();
}
private loadFromDisk(): void {
console.log(`Loading image from disk: ${this.filename}`);
// 模拟加载时间
const loadTime = Math.random() * 1000 + 500;
console.log(`Image loaded in ${loadTime.toFixed(0)}ms`);
}
display(): void {
console.log(`Displaying image: ${this.filename}`);
}
getSize(): number {
return this.size;
}
}
// 代理 - 图片代理
class ImageProxy implements Image {
private realImage: RealImage | null = null;
private filename: string;
constructor(filename: string) {
this.filename = filename;
}
display(): void {
if (this.realImage === null) {
console.log('Creating real image...');
this.realImage = new RealImage(this.filename);
}
this.realImage.display();
}
getSize(): number {
// 可以返回缓存的大小信息,而不需要加载真实图片
if (this.realImage === null) {
console.log('Getting size from metadata...');
return 0; // 从元数据获取
}
return this.realImage.getSize();
}
}
// 使用示例
console.log('=== Image Proxy Demo ===');
const image1: Image = new ImageProxy('photo1.jpg');
const image2: Image = new ImageProxy('photo2.jpg');
console.log('\n--- First display ---');
image1.display(); // 这时才会加载真实图片
console.log('\n--- Second display ---');
image1.display(); // 直接使用已加载的图片
console.log('\n--- Getting size ---');
console.log(`Size: ${image2.getSize()}`); // 不会加载图片
访问控制代理
typescript
// 银行账户接口
interface BankAccount {
deposit(amount: number): void;
withdraw(amount: number): boolean;
getBalance(): number;
getAccountInfo(): string;
}
// 真实银行账户
class RealBankAccount implements BankAccount {
private balance: number = 0;
private accountNumber: string;
private ownerName: string;
constructor(accountNumber: string, ownerName: string, initialBalance: number = 0) {
this.accountNumber = accountNumber;
this.ownerName = ownerName;
this.balance = initialBalance;
}
deposit(amount: number): void {
if (amount > 0) {
this.balance += amount;
console.log(`Deposited $${amount}. New balance: $${this.balance}`);
}
}
withdraw(amount: number): boolean {
if (amount > 0 && amount <= this.balance) {
this.balance -= amount;
console.log(`Withdrew $${amount}. New balance: $${this.balance}`);
return true;
}
console.log(`Withdrawal failed. Insufficient funds or invalid amount.`);
return false;
}
getBalance(): number {
return this.balance;
}
getAccountInfo(): string {
return `Account: ${this.accountNumber}, Owner: ${this.ownerName}, Balance: $${this.balance}`;
}
}
// 用户类
class User {
constructor(
public name: string,
public role: 'owner' | 'authorized' | 'guest' = 'guest'
) {}
}
// 银行账户代理 - 访问控制
class BankAccountProxy implements BankAccount {
private realAccount: RealBankAccount;
private currentUser: User;
constructor(realAccount: RealBankAccount, currentUser: User) {
this.realAccount = realAccount;
this.currentUser = currentUser;
}
private checkPermission(operation: string): boolean {
console.log(`Checking permission for ${this.currentUser.name} (${this.currentUser.role}) to ${operation}`);
switch (operation) {
case 'deposit':
case 'withdraw':
return this.currentUser.role === 'owner' || this.currentUser.role === 'authorized';
case 'getBalance':
return this.currentUser.role !== 'guest';
case 'getAccountInfo':
return this.currentUser.role === 'owner';
default:
return false;
}
}
deposit(amount: number): void {
if (this.checkPermission('deposit')) {
this.realAccount.deposit(amount);
} else {
console.log('❌ Access denied: Insufficient permissions to deposit');
}
}
withdraw(amount: number): boolean {
if (this.checkPermission('withdraw')) {
return this.realAccount.withdraw(amount);
} else {
console.log('❌ Access denied: Insufficient permissions to withdraw');
return false;
}
}
getBalance(): number {
if (this.checkPermission('getBalance')) {
return this.realAccount.getBalance();
} else {
console.log('❌ Access denied: Insufficient permissions to view balance');
return -1;
}
}
getAccountInfo(): string {
if (this.checkPermission('getAccountInfo')) {
return this.realAccount.getAccountInfo();
} else {
console.log('❌ Access denied: Insufficient permissions to view account info');
return 'Access Denied';
}
}
}
// 使用示例
console.log('\n=== Bank Account Proxy Demo ===');
const realAccount = new RealBankAccount('12345', 'John Doe', 1000);
// 不同角色的用户
const owner = new User('John Doe', 'owner');
const authorized = new User('Jane Smith', 'authorized');
const guest = new User('Bob Wilson', 'guest');
console.log('\n--- Owner Access ---');
const ownerProxy = new BankAccountProxy(realAccount, owner);
ownerProxy.deposit(200);
ownerProxy.withdraw(100);
console.log(`Balance: $${ownerProxy.getBalance()}`);
console.log(ownerProxy.getAccountInfo());
console.log('\n--- Authorized User Access ---');
const authorizedProxy = new BankAccountProxy(realAccount, authorized);
authorizedProxy.deposit(50);
console.log(`Balance: $${authorizedProxy.getBalance()}`);
authorizedProxy.getAccountInfo(); // 应该被拒绝
console.log('\n--- Guest Access ---');
const guestProxy = new BankAccountProxy(realAccount, guest);
guestProxy.deposit(100); // 应该被拒绝
guestProxy.getBalance(); // 应该被拒绝
缓存代理
typescript
// 数据服务接口
interface DataService {
getData(key: string): Promise<any>;
setData(key: string, value: any): Promise<void>;
}
// 真实数据服务
class RealDataService implements DataService {
private database: Map<string, any> = new Map();
async getData(key: string): Promise<any> {
console.log(`Fetching data from database for key: ${key}`);
// 模拟数据库查询延迟
await new Promise(resolve => setTimeout(resolve, 1000));
const data = this.database.get(key);
console.log(`Database query completed for key: ${key}`);
return data || null;
}
async setData(key: string, value: any): Promise<void> {
console.log(`Saving data to database for key: ${key}`);
// 模拟数据库写入延迟
await new Promise(resolve => setTimeout(resolve, 500));
this.database.set(key, value);
console.log(`Data saved to database for key: ${key}`);
}
}
// 缓存代理
class CachedDataServiceProxy implements DataService {
private realService: RealDataService;
private cache: Map<string, { data: any; timestamp: number }> = new Map();
private cacheTimeout: number = 5000; // 5秒缓存过期
constructor(realService: RealDataService) {
this.realService = realService;
}
async getData(key: string): Promise<any> {
const cached = this.cache.get(key);
const now = Date.now();
// 检查缓存是否存在且未过期
if (cached && (now - cached.timestamp) < this.cacheTimeout) {
console.log(`Cache hit for key: ${key}`);
return cached.data;
}
console.log(`Cache miss for key: ${key}`);
const data = await this.realService.getData(key);
// 缓存数据
this.cache.set(key, { data, timestamp: now });
return data;
}
async setData(key: string, value: any): Promise<void> {
await this.realService.setData(key, value);
// 更新缓存
this.cache.set(key, { data: value, timestamp: Date.now() });
console.log(`Cache updated for key: ${key}`);
}
clearCache(): void {
this.cache.clear();
console.log('Cache cleared');
}
getCacheStats(): { size: number; keys: string[] } {
return {
size: this.cache.size,
keys: Array.from(this.cache.keys())
};
}
}
// 使用示例
async function demonstrateCacheProxy() {
console.log('\n=== Cache Proxy Demo ===');
const realService = new RealDataService();
const cachedService = new CachedDataServiceProxy(realService);
// 设置一些数据
await cachedService.setData('user:1', { name: 'John', age: 30 });
await cachedService.setData('user:2', { name: 'Jane', age: 25 });
console.log('\n--- First access (cache miss) ---');
const user1 = await cachedService.getData('user:1');
console.log('Retrieved:', user1);
console.log('\n--- Second access (cache hit) ---');
const user1Again = await cachedService.getData('user:1');
console.log('Retrieved:', user1Again);
console.log('\n--- Cache stats ---');
console.log(cachedService.getCacheStats());
console.log('\n--- Waiting for cache to expire ---');
await new Promise(resolve => setTimeout(resolve, 6000));
console.log('\n--- Access after cache expiry ---');
const user1Expired = await cachedService.getData('user:1');
console.log('Retrieved:', user1Expired);
}
// demonstrateCacheProxy();
智能引用代理
typescript
// 文件接口
interface FileSystem {
readFile(filename: string): string;
writeFile(filename: string, content: string): void;
deleteFile(filename: string): void;
}
// 真实文件系统
class RealFileSystem implements FileSystem {
private files: Map<string, string> = new Map();
readFile(filename: string): string {
console.log(`Reading file: ${filename}`);
return this.files.get(filename) || '';
}
writeFile(filename: string, content: string): void {
console.log(`Writing file: ${filename}`);
this.files.set(filename, content);
}
deleteFile(filename: string): void {
console.log(`Deleting file: ${filename}`);
this.files.delete(filename);
}
}
// 智能引用代理
class SmartFileSystemProxy implements FileSystem {
private realFileSystem: RealFileSystem;
private accessLog: { operation: string; filename: string; timestamp: Date }[] = [];
private referenceCount: Map<string, number> = new Map();
constructor(realFileSystem: RealFileSystem) {
this.realFileSystem = realFileSystem;
}
private logAccess(operation: string, filename: string): void {
this.accessLog.push({
operation,
filename,
timestamp: new Date()
});
console.log(`[LOG] ${operation} operation on ${filename} at ${new Date().toISOString()}`);
}
private incrementReference(filename: string): void {
const count = this.referenceCount.get(filename) || 0;
this.referenceCount.set(filename, count + 1);
}
private decrementReference(filename: string): void {
const count = this.referenceCount.get(filename) || 0;
if (count > 0) {
this.referenceCount.set(filename, count - 1);
}
}
readFile(filename: string): string {
this.logAccess('READ', filename);
this.incrementReference(filename);
const content = this.realFileSystem.readFile(filename);
if (!content) {
console.log(`⚠️ Warning: File ${filename} not found`);
}
return content;
}
writeFile(filename: string, content: string): void {
this.logAccess('WRITE', filename);
// 检查内容是否为空
if (!content.trim()) {
console.log(`⚠️ Warning: Writing empty content to ${filename}`);
}
this.realFileSystem.writeFile(filename, content);
this.incrementReference(filename);
}
deleteFile(filename: string): void {
const refCount = this.referenceCount.get(filename) || 0;
if (refCount > 0) {
console.log(`⚠️ Warning: File ${filename} has ${refCount} active references`);
console.log('Are you sure you want to delete it? (Proceeding anyway for demo)');
}
this.logAccess('DELETE', filename);
this.realFileSystem.deleteFile(filename);
this.referenceCount.delete(filename);
}
getAccessLog(): { operation: string; filename: string; timestamp: Date }[] {
return [...this.accessLog];
}
getReferenceCount(filename: string): number {
return this.referenceCount.get(filename) || 0;
}
getStats(): void {
console.log('\n=== File System Stats ===');
console.log(`Total operations: ${this.accessLog.length}`);
console.log('Reference counts:');
this.referenceCount.forEach((count, filename) => {
console.log(` ${filename}: ${count} references`);
});
console.log('\nRecent operations:');
this.accessLog.slice(-5).forEach(log => {
console.log(` ${log.timestamp.toISOString()}: ${log.operation} ${log.filename}`);
});
}
}
// 使用示例
console.log('\n=== Smart File System Proxy Demo ===');
const realFS = new RealFileSystem();
const smartFS = new SmartFileSystemProxy(realFS);
// 文件操作
smartFS.writeFile('document.txt', 'Hello World');
smartFS.writeFile('config.json', '{"theme": "dark"}');
smartFS.readFile('document.txt');
smartFS.readFile('document.txt'); // 再次读取
smartFS.readFile('nonexistent.txt'); // 读取不存在的文件
smartFS.writeFile('empty.txt', ' '); // 写入空内容
smartFS.deleteFile('document.txt'); // 删除有引用的文件
smartFS.getStats();
⚖️ 优缺点分析
✅ 优点
- 控制访问:可以控制对真实对象的访问
- 延迟加载:可以延迟创建开销大的对象
- 附加功能:可以在访问时添加额外的功能
- 透明性:客户端无需知道代理的存在
❌ 缺点
- 复杂性:增加了系统的复杂性
- 性能开销:可能会增加访问的延迟
- 间接性:增加了一层间接访问
🔄 相关模式
- 装饰器模式:装饰器增强功能,代理控制访问
- 适配器模式:适配器改变接口,代理保持接口不变
- 外观模式:外观简化接口,代理控制访问
🚀 最佳实践
- 接口一致性:代理应该与真实对象有相同的接口
- 透明性:客户端应该无法区分代理和真实对象
- 职责单一:每个代理应该有明确的职责
- 性能考虑:避免代理成为性能瓶颈
⚠️ 注意事项
- 循环引用:避免代理和真实对象之间的循环引用
- 线程安全:在多线程环境中确保代理的线程安全
- 内存泄漏:注意代理持有的引用可能导致内存泄漏
- 过度使用:不要为了使用模式而使用模式
📚 总结
代理模式为对象访问提供了一种控制机制,可以在不改变原始类接口的情况下,为原始类定义一个代理类来控制访问。它在需要控制访问、延迟加载、缓存等场景中非常有用。
使用建议:
- 当需要控制对对象的访问时使用
- 当需要延迟创建开销大的对象时使用
- 当需要在访问对象时添加额外功能时使用
- 当需要为远程对象提供本地代表时使用
相关链接: