普通视图

发现新文章,点击刷新页面。
昨天 — 2025年9月26日首页

【TS 设计模式完全指南】构建你的专属“通知中心”:深入观察者模式

作者 烛阴
2025年9月26日 22:55

一、什么是观察者模式?

观察者模式(Observer Pattern)是一种行为设计模式,它定义了一种一对多的依赖关系。当一个对象(被称为主题 Subject发布者 Publisher)的状态发生改变时,所有依赖于它的对象(被称为观察者 Observers订阅者 Subscribers)都会得到通知并自动更新。

二、观察者模式的核心组件

  1. 主题接口 (Subject Interface):声明了用于管理观察者的方法,通常包括 subscribe() , unsubscribe() , 和 notify()
  2. 观察者接口 (Observer Interface):声明了所有具体观察者必须实现的通知方法,通常是 update()
  3. 具体主题 (Concrete Subject):实现了主题接口。它维护着一个观察者列表,并在自身状态改变时,调用 notify() 方法通知所有观察者。
  4. 具体观察者 (Concrete Observer):实现了观察者接口。在 update() 方法中定义了收到通知后要执行的具体逻辑。

三、示例:实现一个商品到货通知系统

3.1 定义接口 (Interfaces)

首先定义好我们的“合同”——SubjectObserver 接口。

// Observer Interface
interface IObserver {
    update(subject: ISubject): void;
}

// Subject Interface
interface ISubject {
    subscribe(observer: IObserver): void;
    unsubscribe(observer: IObserver): void;
    notify(): void;
}
  • update 方法传入 subject 本身,这样观察者就可以在需要时从中获取更新后的状态。

3.2 创建具体主题 (Concrete Subject)

我们的 Product 类就是具体主题。它管理自己的库存状态和订阅者列表。

// Concrete Subject
class Product implements ISubject {
    public readonly name: string;
    private observers: IObserver[] = [];
    private _inStock: boolean = false;

    constructor(name: string) {
        this.name = name;
    }

    get inStock(): boolean {
        return this._inStock;
    }

    // 状态改变的方法
    public setStockStatus(status: boolean): void {
        console.log(`\n[PRODUCT]:商品 "${this.name}" 的库存状态变为 ${status ? '有货' : '缺货'}.`);
        this._inStock = status;
        this.notify(); // 状态改变,通知所有观察者!
    }

    public subscribe(observer: IObserver): void {
        const isExist = this.observers.includes(observer);
        if (isExist) {
            return console.log('Observer has been attached already.');
        }
        this.observers.push(observer);
        console.log(`[SYSTEM]: ${observer.constructor.name} 成功订阅 "${this.name}".`);
    }

    public unsubscribe(observer: IObserver): void {
        const observerIndex = this.observers.indexOf(observer);
        if (observerIndex === -1) {
            return console.log('Nonexistent observer.');
        }
        this.observers.splice(observerIndex, 1);
        console.log(`[SYSTEM]: ${observer.constructor.name} 已取消订阅.`);
    }

    public notify(): void {
        console.log(`[PRODUCT]: 正在通知所有 ${this.observers.length} 位订阅者...`);
        for (const observer of this.observers) {
            observer.update(this);
        }
    }
}

3.3 创建具体观察者 (Concrete Observers)

现在,我们创建UI更新器和邮件服务,它们都是观察者。

// Concrete Observer 1: UI Updater
class UINotifier implements IObserver {
    update(subject: ISubject): void {
        if (subject instanceof Product) {
            console.log(
                `[UI]: 收到通知!商品 "${subject.name}" 现在 ${
                    subject.inStock ? '有货' : '缺货'
                },正在更新页面显示...`
            );
        }
    }
}

// Concrete Observer 2: Email Service
class EmailNotifier implements IObserver {
    update(subject: ISubject): void {
        if (subject instanceof Product && subject.inStock) {
            console.log(`[Email]: 收到通知!商品 "${subject.name}" 已到货,正在准备发送邮件...`);
        }
    }
}

3.4 客户端代码:将一切联系起来

const ps5 = new Product('PlayStation 5');

const ui = new UINotifier();
const emailService = new EmailNotifier();

// 订阅
ps5.subscribe(ui);
ps5.subscribe(emailService);

// 状态变化 -> 缺货 (假设初始为缺货,这里为了演示,手动设置一次)
ps5.setStockStatus(false); 
// 此时只会触发UI更新缺货状态,邮件服务因为逻辑判断不会发送邮件

// 关键时刻:到货了!
ps5.setStockStatus(true);
// 此时UI和Email服务都会收到通知并执行相应操作

// 一个用户不再关心了,取消订阅邮件
ps5.unsubscribe(emailService);

// 再次变为缺货
ps5.setStockStatus(false);
// 这次只有UI会收到通知

为了方便大家学习和实践,本文的所有示例代码和完整项目结构都已整理上传至我的 GitHub 仓库。欢迎大家克隆、研究、提出 Issue,共同进步!

📂 核心代码与完整示例: GoF

总结

如果你喜欢本教程,记得点赞+收藏!关注我获取更多JavaScript/TypeScript开发干货

昨天以前首页

【TS 设计模式完全指南】重构利器:用策略模式解耦你 TypeScript 项目中复杂的分支逻辑

作者 烛阴
2025年9月21日 20:38

一、什么是策略模式?

策略模式(Strategy Pattern)是一种行为设计模式,它定义了一系列算法,并将每个算法封装起来,使它们可以互相替换。策略模式让算法的变化独立于使用它的客户端。

简单来说,就是将不同的处理“策略”(算法)抽象出来,放到各自独立的类中。使用这些策略的“上下文”(Context)对象,可以在运行时根据需要,动态地选择并使用其中任何一个策略。

二、策略模式的核心组件

  1. 策略 (Strategy):通常是一个接口(interface),它定义了所有支持的算法的公共操作。
  2. 具体策略 (Concrete Strategy):实现了策略接口的类,封装了具体的算法或行为。
  3. 上下文 (Context):持有一个策略对象的引用。它不直接执行任务,而是将任务委托给它所持有的策略对象来执行。上下文提供一个方法让客户端可以设置或更换策略。

三、示例:构建一个灵活的表单验证器

3.1 定义策略接口 (Strategy)

所有验证规则都必须遵循这个接口。

// Strategy: 验证策略的统一接口
interface IValidationStrategy {
    validate(value: string): { isValid: boolean; message: string };
}

3.2 创建具体策略 (Concrete Strategies)

现在我们来实现几个具体的验证规则。

// Concrete Strategy 1: 非空验证
class RequiredStrategy implements IValidationStrategy {
    validate(value: string) {
        const isValid = value.trim() !== '';
        return {
            isValid,
            message: isValid ? '' : '此字段为必填项。',
        };
    }
}

// Concrete Strategy 2: 邮箱格式验证
class EmailStrategy implements IValidationStrategy {
    validate(value: string) {
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        const isValid = emailRegex.test(value);
        return {
            isValid,
            message: isValid ? '' : '请输入有效的邮箱地址。',
        };
    }
}

// Concrete Strategy 3: 最小长度验证
class MinLengthStrategy implements IValidationStrategy {
    private minLength: number;

    constructor(minLength: number) {
        this.minLength = minLength;
    }

    validate(value: string) {
        const isValid = value.length >= this.minLength;
        return {
            isValid,
            message: isValid ? '' : `长度不能少于 ${this.minLength} 个字符。`,
        };
    }
}

3.3 创建上下文 (Context)

FormField 类就是我们的上下文。它持有一个验证策略,并用它来执行验证。

// Context: 使用验证策略的表单字段
class FormField {
    private value: string;
    private strategy: IValidationStrategy;

    constructor(initialValue: string, strategy: IValidationStrategy) {
        this.value = initialValue;
        this.strategy = strategy;
    }

    // 允许在运行时更改验证策略
    setValidationStrategy(strategy: IValidationStrategy) {
        this.strategy = strategy;
    }

    setValue(value: string) {
        this.value = value;
    }

    // 委托给策略对象执行验证
    public validate() {
        return this.strategy.validate(this.value);
    }
}

3.4 将它们组合起来,感受动态切换的魅力

现在,我们可以轻松地创建和验证表单字段了。

// --- 客户端代码 ---

// 创建一个需要非空验证的用户名输入框
const usernameField = new FormField('', new RequiredStrategy());
console.log('用户名初始验证:', usernameField.validate()); // { isValid: false, message: '此字段为必填项。' }

usernameField.setValue('JohnDoe');
console.log('用户名输入后验证:', usernameField.validate()); // { isValid: true, message: '' }

console.log('\n' + '='.repeat(30) + '\n');

// 创建一个需要邮箱格式验证的邮箱输入框
const emailField = new FormField('not-an-email', new EmailStrategy());
console.log('邮箱格式错误验证:', emailField.validate()); // { isValid: false, message: '请输入有效的邮箱地址。' }

emailField.setValue('test@example.com');
console.log('邮箱格式正确验证:', emailField.validate()); // { isValid: true, message: '' }

console.log('\n' + '='.repeat(30) + '\n');

// 创建一个需要密码长度验证的密码输入框
const passwordField = new FormField('123', new MinLengthStrategy(8));
console.log('密码长度不足验证:', passwordField.validate()); // { isValid: false, message: '长度不能少于 8 个字符。' }

// 假设用户注册后,需要增加更强的密码策略,我们可以动态切换!
// passwordField.setValidationStrategy(new StrongPasswordStrategy()); // 假设我们新增了一个更复杂的策略
passwordField.setValue('password123');
console.log('密码长度足够验证:', passwordField.validate()); // { isValid: true, message: '' }
  • 如果未来我们需要增加“手机号格式验证”或“身份证号验证”,只需创建一个新的策略类实现 IValidationStrategy 接口即可,FormField 类完全不需要任何改动。

为了方便大家学习和实践,本文的所有示例代码和完整项目结构都已整理上传至我的 GitHub 仓库。欢迎大家克隆、研究、提出 Issue,共同进步!

📂 核心代码与完整示例: GoF

总结

如果你喜欢本教程,记得点赞+收藏!关注我获取更多JavaScript/TypeScript开发干货

❌
❌