JavaScript设计模式

掌握单例、工厂、观察者等常用设计模式及其应用场景

什么是设计模式?

设计模式是解决特定问题的可重用方案,是软件开发人员在长期实践中总结出的最佳实践。在JavaScript中,设计模式可以帮助我们编写更清晰、可维护和可扩展的代码。

设计模式的重要性: 使用设计模式可以提高代码的可读性、可维护性和复用性,降低代码的耦合度,使系统更加灵活和易于扩展。设计模式是面向对象设计的核心经验总结,能够帮助开发者构建更加健壮的软件架构。

设计模式的起源

设计模式的概念最早由建筑师克里斯托弗·亚历山大提出,用于描述建筑设计的通用解决方案。1994年,埃里希·伽玛等四位作者(被称为"四人帮")将这一概念引入软件工程领域,出版了《设计模式:可复用面向对象软件的基础》一书,奠定了软件设计模式的基础。

设计模式的核心原则

  • 开闭原则 - 对扩展开放,对修改关闭
  • 单一职责原则 - 一个类只负责一个功能领域
  • 里氏替换原则 - 子类可以替换父类并保持程序正确性
  • 接口隔离原则 - 使用多个专门的接口比使用单一的总接口要好
  • 依赖倒置原则 - 依赖于抽象而不是具体实现

设计模式分类

设计模式通常分为三大类:创建型模式、结构型模式和行为型模式。

创建型模式

关注对象创建机制,以合适的方式创建对象

  • 单例模式
  • 工厂模式
  • 抽象工厂模式
  • 建造者模式
  • 原型模式

结构型模式

关注类和对象的组合,形成更大的结构

  • 适配器模式
  • 装饰器模式
  • 代理模式
  • 外观模式
  • 桥接模式
  • 组合模式
  • 享元模式

行为型模式

关注对象之间的职责分配和通信

  • 观察者模式
  • 策略模式
  • 命令模式
  • 状态模式
  • 职责链模式
  • 解释器模式
  • 迭代器模式
  • 中介者模式
  • 备忘录模式
  • 模板方法模式
  • 访问者模式

JavaScript特有的设计模式

除了传统的面向对象设计模式,JavaScript还有一些特有的模式,充分利用了JavaScript的语言特性:

  • 模块模式 - 利用闭包创建私有变量和方法
  • 揭示模块模式 - 模块模式的变体,明确公开API
  • 构造函数模式 - 使用构造函数创建对象
  • 原型模式 - 利用原型链实现继承
  • 混入模式 - 通过复制属性实现多重继承

创建型模式

创建型模式关注对象的创建机制,帮助我们以更灵活、更合适的方式创建对象,而不是直接使用new操作符。

1. 单例模式 (Singleton)

确保一个类只有一个实例,并提供全局访问点。单例模式常用于管理全局状态、配置信息、日志记录器等场景。

单例模式实现
class Logger {
    constructor() {
        if (Logger.instance) {
            return Logger.instance;
        }
        
        this.logs = [];
        Logger.instance = this;
        
        return this;
    }
    
    log(message) {
        const timestamp = new Date().toISOString();
        const logEntry = { message, timestamp };
        this.logs.push(logEntry);
        console.log(`[${timestamp}] ${message}`);
    }
    
    getLogs() {
        return this.logs;
    }
    
    clearLogs() {
        this.logs = [];
    }
}

// 使用示例
const logger1 = new Logger();
const logger2 = new Logger();

console.log(logger1 === logger2); // true - 同一个实例

logger1.log('第一条日志');
logger2.log('第二条日志');

console.log(logger1.getLogs()); // 包含两条日志

// 现代ES6实现
const ConfigManager = (function() {
    let instance;
    let config = {};
    
    function createInstance() {
        return {
            set(key, value) {
                config[key] = value;
            },
            get(key) {
                return config[key];
            },
            getAll() {
                return { ...config };
            }
        };
    }
    
    return {
        getInstance() {
            if (!instance) {
                instance = createInstance();
            }
            return instance;
        }
    };
})();

const config1 = ConfigManager.getInstance();
const config2 = ConfigManager.getInstance();
console.log(config1 === config2); // true

单例模式应用场景

  • 全局配置管理
  • 日志记录器
  • 数据库连接池
  • 应用程序的状态管理
  • 缓存系统

2. 工厂模式 (Factory)

创建对象而不暴露创建逻辑,通过工厂方法创建对象。工厂模式将对象的创建和使用分离,提高了代码的灵活性和可维护性。

工厂模式实现
// 简单工厂
class ButtonFactory {
    static createButton(type) {
        switch (type) {
            case 'primary':
                return new PrimaryButton();
            case 'secondary':
                return new SecondaryButton();
            case 'danger':
                return new DangerButton();
            default:
                throw new Error(`未知按钮类型: ${type}`);
        }
    }
}

class PrimaryButton {
    render() {
        return '<button class="btn btn-primary">主要按钮</button>';
    }
}

class SecondaryButton {
    render() {
        return '<button class="btn btn-secondary">次要按钮</button>';
    }
}

class DangerButton {
    render() {
        return '<button class="btn btn-danger">危险按钮</button>';
    }
}

// 使用示例
const primaryBtn = ButtonFactory.createButton('primary');
console.log(primaryBtn.render());

// 抽象工厂
class UIFactory {
    createButton() {
        throw new Error('必须实现createButton方法');
    }
    
    createInput() {
        throw new Error('必须实现createInput方法');
    }
}

class LightThemeFactory extends UIFactory {
    createButton() {
        return new LightButton();
    }
    
    createInput() {
        return new LightInput();
    }
}

class DarkThemeFactory extends UIFactory {
    createButton() {
        return new DarkButton();
    }
    
    createInput() {
        return new DarkInput();
    }
}

class LightButton {
    render() {
        return '浅色主题按钮';
    }
}

class DarkButton {
    render() {
        return '深色主题按钮';
    }
}

工厂模式应用场景

  • 创建复杂对象
  • 需要根据不同条件创建不同对象
  • 隐藏对象创建的复杂性
  • 对象池管理
  • 数据库连接创建

3. 建造者模式 (Builder)

将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。

建造者模式实现
class Pizza {
    constructor() {
        this.size = '';
        this.crust = '';
        this.toppings = [];
    }
    
    describe() {
        return `一个${this.size}英寸的${this.crust}比萨,配料:${this.toppings.join(', ')}`;
    }
}

class PizzaBuilder {
    constructor() {
        this.pizza = new Pizza();
    }
    
    setSize(size) {
        this.pizza.size = size;
        return this;
    }
    
    setCrust(crust) {
        this.pizza.crust = crust;
        return this;
    }
    
    addTopping(topping) {
        this.pizza.toppings.push(topping);
        return this;
    }
    
    build() {
        return this.pizza;
    }
}

// 使用示例
const myPizza = new PizzaBuilder()
    .setSize(12)
    .setCrust('薄脆')
    .addTopping('芝士')
    .addTopping('蘑菇')
    .addTopping('火腿')
    .build();

console.log(myPizza.describe());

结构型模式

结构型模式关注类和对象的组合,帮助我们构建更大的结构,同时保持结构的灵活性和效率。

4. 装饰器模式 (Decorator)

动态地为对象添加新功能,而不改变其结构。装饰器模式是继承的一个替代方案,提供了比继承更灵活的功能扩展方式。

装饰器模式实现
// 基础组件
class Coffee {
    cost() {
        return 5;
    }
    
    description() {
        return '普通咖啡';
    }
}

// 装饰器基类
class CoffeeDecorator {
    constructor(coffee) {
        this.coffee = coffee;
    }
    
    cost() {
        return this.coffee.cost();
    }
    
    description() {
        return this.coffee.description();
    }
}

// 具体装饰器
class MilkDecorator extends CoffeeDecorator {
    cost() {
        return this.coffee.cost() + 2;
    }
    
    description() {
        return this.coffee.description() + ', 加牛奶';
    }
}

class SugarDecorator extends CoffeeDecorator {
    cost() {
        return this.coffee.cost() + 1;
    }
    
    description() {
        return this.coffee.description() + ', 加糖';
    }
}

class WhippedCreamDecorator extends CoffeeDecorator {
    cost() {
        return this.coffee.cost() + 3;
    }
    
    description() {
        return this.coffee.description() + ', 加奶油';
    }
}

// 使用示例
let myCoffee = new Coffee();
console.log(myCoffee.description(), '- 价格:', myCoffee.cost());

myCoffee = new MilkDecorator(myCoffee);
console.log(myCoffee.description(), '- 价格:', myCoffee.cost());

myCoffee = new SugarDecorator(myCoffee);
console.log(myCoffee.description(), '- 价格:', myCoffee.cost());

myCoffee = new WhippedCreamDecorator(myCoffee);
console.log(myCoffee.description(), '- 价格:', myCoffee.cost());

// ES7装饰器语法 (需要Babel转译)
function log(target, name, descriptor) {
    const original = descriptor.value;
    
    descriptor.value = function(...args) {
        console.log(`调用 ${name} 方法, 参数:`, args);
        const result = original.apply(this, args);
        console.log(`方法 ${name} 返回:`, result);
        return result;
    };
    
    return descriptor;
}

class Calculator {
    @log
    add(a, b) {
        return a + b;
    }
}

5. 适配器模式 (Adapter)

使不兼容的接口能够一起工作。适配器模式充当两个不兼容接口之间的桥梁,让它们能够协同工作。

适配器模式实现
// 旧系统接口
class OldPaymentSystem {
    makePayment(amount, currency) {
        console.log(`使用旧系统支付: ${amount} ${currency}`);
        return `OLD_${Date.now()}`;
    }
}

// 新系统期望的接口
class NewPaymentGateway {
    processPayment(paymentDetails) {
        // 期望接收 { amount: number, currency: string, method: string }
        console.log(`使用新网关支付: ${paymentDetails.amount} ${paymentDetails.currency}`);
        return `NEW_${Date.now()}`;
    }
}

// 适配器
class PaymentAdapter {
    constructor(oldSystem) {
        this.oldSystem = oldSystem;
    }
    
    processPayment(paymentDetails) {
        // 将新接口转换为旧接口
        const { amount, currency } = paymentDetails;
        return this.oldSystem.makePayment(amount, currency);
    }
}

// 使用示例
const oldSystem = new OldPaymentSystem();
const adapter = new PaymentAdapter(oldSystem);

// 新系统可以使用适配器
const newGateway = new NewPaymentGateway();

// 模拟支付处理
function processPayment(gateway, payment) {
    return gateway.processPayment(payment);
}

// 使用适配器让旧系统兼容新接口
const result1 = processPayment(adapter, { 
    amount: 100, 
    currency: 'USD', 
    method: 'credit' 
});

const result2 = processPayment(newGateway, { 
    amount: 200, 
    currency: 'EUR', 
    method: 'paypal' 
});

console.log('支付结果:', result1, result2);

6. 代理模式 (Proxy)

为其他对象提供一种代理以控制对这个对象的访问。代理模式可以在不改变原始对象的情况下,提供额外的功能。

代理模式实现
// 真实对象
class RealImage {
    constructor(filename) {
        this.filename = filename;
        this.loadFromDisk();
    }
    
    loadFromDisk() {
        console.log(`加载图片: ${this.filename}`);
    }
    
    display() {
        console.log(`显示图片: ${this.filename}`);
    }
}

// 代理对象
class ProxyImage {
    constructor(filename) {
        this.filename = filename;
        this.realImage = null;
    }
    
    display() {
        if (this.realImage === null) {
            this.realImage = new RealImage(this.filename);
        }
        this.realImage.display();
    }
}

// 使用示例
const image = new ProxyImage('test.jpg');
// 图片此时还没有加载
image.display(); // 加载并显示图片
// 再次显示,不会重新加载
image.display();

行为型模式

行为型模式关注对象之间的职责分配和通信,帮助我们设计对象之间的交互和职责分配。

7. 观察者模式 (Observer)

定义对象间的一对多依赖关系,当一个对象状态改变时,所有依赖它的对象都会得到通知。

观察者模式实现
class Subject {
    constructor() {
        this.observers = [];
    }
    
    addObserver(observer) {
        this.observers.push(observer);
    }
    
    removeObserver(observer) {
        const index = this.observers.indexOf(observer);
        if (index > -1) {
            this.observers.splice(index, 1);
        }
    }
    
    notify(data) {
        this.observers.forEach(observer => observer.update(data));
    }
}

class Observer {
    constructor(name) {
        this.name = name;
    }
    
    update(data) {
        console.log(`${this.name} 收到通知:`, data);
    }
}

// 使用示例
const newsAgency = new Subject();

const subscriber1 = new Observer('订阅者1');
const subscriber2 = new Observer('订阅者2');
const subscriber3 = new Observer('订阅者3');

newsAgency.addObserver(subscriber1);
newsAgency.addObserver(subscriber2);
newsAgency.addObserver(subscriber3);

// 发布新闻
newsAgency.notify('今天天气晴朗');
newsAgency.removeObserver(subscriber2);
newsAgency.notify('明天有雨');

// 更现代的EventEmitter实现
class EventEmitter {
    constructor() {
        this.events = {};
    }
    
    on(event, listener) {
        if (!this.events[event]) {
            this.events[event] = [];
        }
        this.events[event].push(listener);
    }
    
    off(event, listener) {
        if (!this.events[event]) return;
        
        const index = this.events[event].indexOf(listener);
        if (index > -1) {
            this.events[event].splice(index, 1);
        }
    }
    
    emit(event, data) {
        if (!this.events[event]) return;
        
        this.events[event].forEach(listener => {
            listener(data);
        });
    }
    
    once(event, listener) {
        const onceWrapper = (data) => {
            listener(data);
            this.off(event, onceWrapper);
        };
        this.on(event, onceWrapper);
    }
}

// 使用EventEmitter
const emitter = new EventEmitter();

const logData = (data) => console.log('收到数据:', data);
const logOnce = (data) => console.log('一次性监听:', data);

emitter.on('data', logData);
emitter.once('data', logOnce);

emitter.emit('data', '第一次发射'); // 两个监听器都会触发
emitter.emit('data', '第二次发射'); // 只有logData会触发

8. 发布-订阅模式 (Pub-Sub)

观察者模式的变体,使用主题/通道进行通信。发布-订阅模式中,发布者和订阅者不知道彼此的存在,通过消息代理进行通信。

发布-订阅模式实现
class PubSub {
    constructor() {
        this.topics = {};
        this.subId = -1;
    }
    
    subscribe(topic, callback) {
        if (!this.topics[topic]) {
            this.topics[topic] = [];
        }
        
        const token = (++this.subId).toString();
        this.topics[topic].push({
            token,
            callback
        });
        
        return token;
    }
    
    publish(topic, data) {
        if (!this.topics[topic]) {
            return false;
        }
        
        this.topics[topic].forEach(subscriber => {
            subscriber.callback(data);
        });
        
        return true;
    }
    
    unsubscribe(token) {
        for (let topic in this.topics) {
            if (this.topics[topic]) {
                for (let i = 0; i < this.topics[topic].length; i++) {
                    if (this.topics[topic][i].token === token) {
                        this.topics[topic].splice(i, 1);
                        return token;
                    }
                }
            }
        }
        return false;
    }
}

// 使用示例
const pubsub = new PubSub();

// 订阅主题
const subscription1 = pubsub.subscribe('user/login', (user) => {
    console.log('发送登录通知邮件给:', user.email);
});

const subscription2 = pubsub.subscribe('user/login', (user) => {
    console.log('记录用户登录日志:', user.username);
});

const subscription3 = pubsub.subscribe('user/logout', (user) => {
    console.log('用户退出:', user.username);
});

// 发布事件
pubsub.publish('user/login', {
    username: 'john_doe',
    email: 'john@example.com',
    loginTime: new Date()
});

// 取消订阅
pubsub.unsubscribe(subscription1);

pubsub.publish('user/login', {
    username: 'jane_doe',
    email: 'jane@example.com',
    loginTime: new Date()
});

pubsub.publish('user/logout', {
    username: 'john_doe'
});

9. 策略模式 (Strategy)

定义一系列算法,将它们封装起来,并且使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户。

策略模式实现
// 策略类
class NormalStrategy {
    calculate(amount) {
        return amount;
    }
}

class DiscountStrategy {
    constructor(discountRate) {
        this.discountRate = discountRate;
    }
    
    calculate(amount) {
        return amount * (1 - this.discountRate);
    }
}

class PremiumStrategy {
    calculate(amount) {
        return amount * 0.8; // 20% 折扣
    }
}

// 上下文类
class ShoppingCart {
    constructor() {
        this.items = [];
        this.strategy = new NormalStrategy();
    }
    
    addItem(item) {
        this.items.push(item);
    }
    
    setStrategy(strategy) {
        this.strategy = strategy;
    }
    
    checkout() {
        const total = this.items.reduce((sum, item) => sum + item.price, 0);
        return this.strategy.calculate(total);
    }
}

// 使用示例
const cart = new ShoppingCart();
cart.addItem({ name: '商品1', price: 100 });
cart.addItem({ name: '商品2', price: 200 });

console.log('普通价格:', cart.checkout()); // 300

cart.setStrategy(new DiscountStrategy(0.1)); // 10% 折扣
console.log('折扣价格:', cart.checkout()); // 270

cart.setStrategy(new PremiumStrategy()); // 20% 折扣
console.log('会员价格:', cart.checkout()); // 240

设计模式比较与选择指南

设计模式 类型 适用场景 优点 缺点
单例模式 创建型 需要全局唯一实例的场景 节省资源,严格控制访问 可能成为全局状态,难以测试
工厂模式 创建型 需要创建复杂对象的场景 封装创建逻辑,提高灵活性 增加了系统复杂度
装饰器模式 结构型 需要动态添加功能的场景 比继承更灵活,符合开闭原则 可能产生过多小对象
适配器模式 结构型 需要兼容不同接口的场景 提高代码复用性 可能增加系统复杂度
观察者模式 行为型 一对多的依赖关系场景 降低耦合度,支持广播通信 可能引起内存泄漏
策略模式 行为型 需要多种算法的场景 避免条件语句,易于扩展 客户端必须了解不同策略
设计模式选择建议:
  • 需要控制对象创建 - 使用创建型模式(单例、工厂、建造者)
  • 需要扩展对象功能 - 使用结构型模式(装饰器、适配器)
  • 需要管理对象间通信 - 使用行为型模式(观察者、策略)
  • 需要简化复杂接口 - 使用外观模式或适配器模式
  • 需要实现算法族 - 使用策略模式
  • 需要实现撤销操作 - 使用备忘录模式

实践练习

👆 请点击上方按钮进行演示操作

选择不同的设计模式演示来探索它们的实现原理和应用场景

设计模式最佳实践

JavaScript设计模式使用建议:
  • 不要过度设计 - 只在确实需要时使用设计模式,避免不必要的复杂性
  • 了解JavaScript特性 - 充分利用JavaScript的动态性、闭包和原型链
  • 优先使用组合而非继承 - JavaScript更适合使用组合和混入模式
  • 考虑性能影响 - 某些设计模式可能带来性能开销,需要权衡
  • 保持代码可读性 - 使用设计模式是为了提高代码质量,不是炫耀技巧

常见设计模式误用

  • 单例模式滥用 - 可能导致全局状态污染和测试困难
  • 过度使用继承 - 在JavaScript中,组合通常比继承更灵活
  • 不必要的抽象 - 过早的抽象可能增加系统复杂度
  • 模式堆砌 - 在一个地方使用太多设计模式,导致代码难以理解

下一步学习

掌握了设计模式后,可以继续学习: