一、什么是代理模式?
代理模式(Proxy Pattern)是一种结构型设计模式 ,它为你提供了一个对象的替代品或占位符,以便控制对原始对象的访问。
二、代理模式的核心组件
- 主题 (Subject):一个接口,定义了真实对象和代理对象的共同行为。客户端通过这个接口与它们交互。
- 真实主题 (Real Subject):实现了主题接口的类,是代理所代表的真实对象。它包含了核心的业务逻辑。
- 代理 (Proxy):同样实现了主题接口。它内部维护一个对真实主题对象的引用,并且可以在调用真实主题的方法前后执行额外的逻辑。
三、示例:下载器的实现
3.1 基础结构
首先,定义我们的Subject
接口和Real Subject
类。
typescript
// Subject: 文件下载器的接口
interface IDownloader {
download(url: string): Promise<string>;
}
// Real Subject: 真实的文件下载器,模拟一个耗时的操作
class RealFileDownloader implements IDownloader {
constructor() {
// 模拟昂贵的构造过程
console.log('RealFileDownloader: 正在初始化,连接服务器...');
// 假设这里有复杂的初始化逻辑
}
public async download(url: string): Promise<string> {
console.log(`RealFileDownloader: 开始从 ${url} 下载文件...`);
// 模拟网络延迟
await new Promise((resolve) => setTimeout(resolve, 2000));
const fileContent = `这是从 ${url} 下载的文件内容`;
console.log('RealFileDownloader: 文件下载完成。');
return fileContent;
}
}
3.2 虚拟代理 (Virtual Proxy) 实现懒加载
我们不希望一创建下载器实例就开始初始化。只有当用户真正调用 download
方法时,才去创建那个RealFileDownloader
。
typescript
// Proxy: 虚拟代理,实现懒加载
class LazyDownloaderProxy implements IDownloader {
private realDownloader: RealFileDownloader | null = null;
private url: string;
constructor(url: string) {
this.url = url;
console.log('LazyDownloaderProxy: 实例已创建,但真实下载器尚未初始化。');
}
public download(): Promise<string> {
// 只有在第一次调用 download 时,才真正创建 RealFileDownloader
if (this.realDownloader === null) {
console.log('LazyDownloaderProxy: 检测到首次下载请求,开始初始化真实下载器。');
this.realDownloader = new RealFileDownloader();
}
return this.realDownloader.download(this.url);
}
}
// --- 使用 ---
async function startDown() {
console.log('--- 懒加载测试 ---');
const lazyProxy = new LazyDownloaderProxy('https://example.com/bigfile.zip');
console.log('代理已创建,现在执行其他操作...');
// ... (可以执行很多其他代码,RealFileDownloader 还未被创建)
console.log('准备调用下载...');
await lazyProxy.download();
}
startDown();
结果分析 :你会看到 RealFileDownloader
的初始化日志是在调用 download()
方法时才打印出来的,完美实现了懒加载,节省了资源。
3.3 保护代理 (Protection Proxy) 实现权限控制
假设只有管理员才能下载文件。我们可以在代理中加入权限检查。
typescript
// Proxy: 保护代理,实现访问控制
class ProtectedDownloaderProxy implements IDownloader {
private realDownloader: RealFileDownloader;
private userRole: 'admin' | 'guest';
constructor(userRole: 'admin' | 'guest') {
this.realDownloader = new RealFileDownloader(); // 这里为了演示,直接创建
this.userRole = userRole;
}
public download(url: string): Promise<string> {
if (this.userRole === 'admin') {
console.log('ProtectedDownloaderProxy: 权限验证通过!');
return this.realDownloader.download(url);
} else {
console.error('ProtectedDownloaderProxy: 权限不足,禁止下载!');
return Promise.reject(new Error('Access Denied'));
}
}
}
// --- 使用 ---
async function startDown() {
console.log('\n--- 权限控制测试 ---');
const adminProxy = new ProtectedDownloaderProxy('admin');
await adminProxy.download('https://example.com/secret.doc');
try {
const guestProxy = new ProtectedDownloaderProxy('guest');
await guestProxy.download('https://example.com/secret.doc');
} catch (error) {
console.log(`捕获到错误: ${(error as Error).message}`);
}
}
startDown();
结果分析 :管理员用户可以成功下载,而访客用户则会被代理拦截并抛出错误,RealFileDownloader
的 download
方法根本不会被执行。
3.4 缓存代理 (Caching Proxy)
对于相同的URL,我们不应该重复下载。代理可以轻松地加入缓存逻辑。
typescript
// Proxy: 缓存代理
class CachingDownloaderProxy implements IDownloader {
private realDownloader: RealFileDownloader;
private cache: Map<string, string> = new Map();
constructor() {
this.realDownloader = new RealFileDownloader();
}
public async download(url: string): Promise<string> {
if (this.cache.has(url)) {
console.log(`CachingDownloaderProxy: 命中缓存,直接返回 ${url} 的内容。`);
return this.cache.get(url)!;
}
console.log(`CachingDownloaderProxy: 未命中缓存,请求真实下载器...`);
const fileContent = await this.realDownloader.download(url);
this.cache.set(url, fileContent); // 下载后存入缓存
return fileContent;
}
}
// --- 使用 ---
async function startDown() {
// --- 使用 ---
console.log('\n--- 缓存测试 ---');
const cacheProxy = new CachingDownloaderProxy();
console.log('第一次下载:');
await cacheProxy.download('https://example.com/logo.png');
console.log('\n第二次下载同一文件:');
await cacheProxy.download('https://example.com/logo.png');
}
startDown();
结果分析 :第二次调用 download
时,你会看到它立即从缓存返回,而没有触发真实下载器的耗时操作。
为了方便大家学习和实践,本文的所有示例代码和完整项目结构都已整理上传至我的 GitHub 仓库。欢迎大家克隆、研究、提出 Issue,共同进步!
📂 核心代码与完整示例: GoF
总结
如果你喜欢本教程,记得点赞+收藏!关注我获取更多JavaScript/TypeScript开发干货