【TS 设计模式完全指南】懒加载、缓存与权限控制:代理模式在 TypeScript 中的三大妙用

一、什么是代理模式?

代理模式(Proxy Pattern)是一种结构型设计模式 ,它为你提供了一个对象的替代品或占位符,以便控制对原始对象的访问。

二、代理模式的核心组件

  1. 主题 (Subject):一个接口,定义了真实对象和代理对象的共同行为。客户端通过这个接口与它们交互。
  2. 真实主题 (Real Subject):实现了主题接口的类,是代理所代表的真实对象。它包含了核心的业务逻辑。
  3. 代理 (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();

结果分析 :管理员用户可以成功下载,而访客用户则会被代理拦截并抛出错误,RealFileDownloaderdownload 方法根本不会被执行。

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开发干货

相关推荐
小高0072 小时前
🔍说说对React的理解?有哪些特性?
前端·javascript·react.js
Samsong2 小时前
JavaScript逆向之反制无限debugger陷阱
前端·javascript
skykun2 小时前
今天你学会JS的类型转换了吗?
javascript
Lotzinfly2 小时前
8 个经过实战检验的 Promise 奇淫技巧你需要掌握😏😏😏
前端·javascript·面试
小桥风满袖3 小时前
极简三分钟ES6 - ES9中对象扩展
前端·javascript
城中的雾3 小时前
HarmonyOS应用拉起系列(三):如何直接拉起腾讯/百度/高德地图进行导航
前端·javascript·harmonyos
Mintopia3 小时前
Next 全栈之 API 测试:Supertest 与 MSW 双雄记 🥷⚔️
前端·javascript·next.js
李广坤3 小时前
工厂模式
设计模式
鹏多多3 小时前
纯前端人脸识别利器:face-api.js手把手深入解析教学
前端·javascript·人工智能