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

相关推荐
冴羽4 分钟前
10 个被严重低估的 JS 特性,直接少写 500 行代码
前端·javascript·性能优化
一 乐26 分钟前
高校后勤报修系统|物业管理|基于SprinBoot+vue的高校后勤报修系统(源码+数据库+文档)
java·前端·javascript·数据库·vue.js·毕设
那年窗外下的雪.1 小时前
鸿蒙ArkUI布局与样式进阶(十五)—— 模块化 · 自定义组件 · 泛型机制深度解析
javascript·华为·typescript·harmonyos·鸿蒙·arkui
Adellle2 小时前
设计模式的介绍
设计模式
达斯维达的大眼睛2 小时前
设计模式-单列模式
设计模式·cpp
一点七加一2 小时前
Harmony鸿蒙开发0基础入门到精通Day09--JavaScript篇
开发语言·javascript·ecmascript
Javatutouhouduan2 小时前
记一次redis主从切换导致的数据丢失与陷入只读状态故障
java·redis·设计模式·java面试·高可用·java后端·java程序员
薛一半2 小时前
Vue3的Pinia详解
前端·javascript·vue.js
盼哥PyAI实验室4 小时前
从搭建到打磨:我的纯前端个人博客开发复盘
前端·javascript
数据知道4 小时前
Go语言设计模式:抽象工厂模式详解
设计模式·golang·抽象工厂模式·go语言