JavaScript设计模式
设计模式是软件开发的智慧结晶,是前辈们在长期实践中总结出的可复用解决方案。在JavaScript开发中,恰当地运用设计模式可以让代码更加优雅、可维护、可扩展。作为一个在代码丛林中摸爬滚打多年的开发者,我深刻体会到:好的设计模式就像魔法咒语,能让混乱的代码变得井然有序。
为什么需要设计模式?
设计模式并非银弹,它们是解决问题的工具而非目的本身。在合适的场景使用合适的模式,能够带来诸多好处:代码复用性提高、可读性增强、维护成本降低、团队协作更顺畅。但过度使用或生搬硬套,反而会让简单的问题复杂化。
JavaScript作为一门灵活的动态语言,实现设计模式的方式与其他语言有所不同。我们可以利用闭包、原型链、高阶函数等特性,以更加简洁优雅的方式实现经典模式。
单例模式
单例模式确保一个类只有一个实例,并提供一个全局访问点。在前端开发中,单例模式常用于管理全局状态、配置对象、弹窗组件等场景。
基础实现
class Singleton {
constructor() {
if (Singleton.instance) {
return Singleton.instance;
}
Singleton.instance = this;
this.data = {};
}
setData(key, value) {
this.data[key] = value;
}
getData(key) {
return this.data[key];
}
}
// 使用
const instance1 = new Singleton();
const instance2 = new Singleton();
console.log(instance1 === instance2); // true
使用闭包实现
const Singleton = (function() {
let instance;
function createInstance() {
return {
data: {},
set(key, value) {
this.data[key] = value;
},
get(key) {
return this.data[key];
}
};
}
return {
getInstance() {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();
const a = Singleton.getInstance();
const b = Singleton.getInstance();
console.log(a === b); // true
实际应用:全局状态管理
class AppState {
constructor() {
if (AppState.instance) {
return AppState.instance;
}
this.state = {
user: null,
theme: 'light',
notifications: []
};
this.listeners = [];
AppState.instance = this;
}
subscribe(listener) {
this.listeners.push(listener);
return () => {
this.listeners = this.listeners.filter(l => l !== listener);
};
}
setState(newState) {
this.state = { ...this.state, ...newState };
this.listeners.forEach(listener => listener(this.state));
}
getState() {
return this.state;
}
}
const appState = new AppState();
工厂模式
工厂模式提供了一种创建对象的最佳方式,在创建对象时不会对客户端暴露创建逻辑。这种模式特别适合需要根据条件创建不同类型对象的场景。
简单工厂
class Button {
constructor(type) {
this.type = type;
}
render() {
return ``;
}
}
class ButtonFactory {
static create(type) {
switch(type) {
case 'primary':
return new Button('btn-primary');
case 'secondary':
return new Button('btn-secondary');
case 'danger':
return new Button('btn-danger');
default:
throw new Error('未知按钮类型');
}
}
}
const primaryBtn = ButtonFactory.create('primary');
const dangerBtn = ButtonFactory.create('danger');
抽象工厂
// 抽象产品
class UIComponent {
render() {
throw new Error('子类必须实现render方法');
}
}
// 具体产品:按钮
class Button extends UIComponent {
constructor(text) {
super();
this.text = text;
}
render() {
return ``;
}
}
// 具体产品:输入框
class Input extends UIComponent {
constructor(placeholder) {
super();
this.placeholder = placeholder;
}
render() {
return ``;
}
}
// 抽象工厂
class UIFactory {
createButton() {
throw new Error('子类必须实现createButton方法');
}
createInput() {
throw new Error('子类必须实现createInput方法');
}
}
// 具体工厂:桌面版UI
class DesktopUIFactory extends UIFactory {
createButton() {
return new Button('桌面按钮');
}
createInput() {
return new Input('请输入内容');
}
}
// 具体工厂:移动版UI
class MobileUIFactory extends UIFactory {
createButton() {
return new Button('移动按钮');
}
createInput() {
return new Input('点击输入');
}
}
// 使用
function createUI(factory) {
const button = factory.createButton();
const input = factory.createInput();
return { button, input };
}
const desktopUI = createUI(new DesktopUIFactory());
const mobileUI = createUI(new MobileUIFactory());
观察者模式
观察者模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这是前端开发中最常用的模式之一。
基础实现
class Subject {
constructor() {
this.observers = [];
}
subscribe(observer) {
this.observers.push(observer);
return () => {
this.observers = this.observers.filter(o => o !== observer);
};
}
notify(data) {
this.observers.forEach(observer => observer.update(data));
}
}
class Observer {
constructor(name) {
this.name = name;
}
update(data) {
console.log(`${this.name} 收到通知:`, data);
}
}
// 使用
const subject = new Subject();
const observer1 = new Observer('观察者1');
const observer2 = new Observer('观察者2');
subject.subscribe(observer1);
subject.subscribe(observer2);
subject.notify('重要消息');
实际应用:事件总线
class EventBus {
constructor() {
this.events = {};
}
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
// 返回取消订阅函数
return () => {
this.events[event] = this.events[event].filter(cb => cb !== callback);
};
}
emit(event, ...args) {
if (this.events[event]) {
this.events[event].forEach(callback => callback(...args));
}
}
once(event, callback) {
const wrapper = (...args) => {
callback(...args);
this.off(event, wrapper);
};
this.on(event, wrapper);
}
off(event, callback) {
if (this.events[event]) {
this.events[event] = this.events[event].filter(cb => cb !== callback);
}
}
}
// 全局事件总线
const eventBus = new EventBus();
// 组件A订阅事件
eventBus.on('user-login', (user) => {
console.log('用户登录:', user);
});
// 组件B触发事件
eventBus.emit('user-login', { name: '张三', id: 1 });
发布订阅模式
发布订阅模式与观察者模式相似,但有一个关键区别:发布者和订阅者之间通过消息代理进行通信,两者不直接接触。这种模式更加解耦,特别适合复杂的消息传递场景。
class PubSub {
constructor() {
this.topics = {};
this.subUid = -1;
}
subscribe(topic, func) {
if (!this.topics[topic]) {
this.topics[topic] = [];
}
const token = (++this.subUid).toString();
this.topics[topic].push({
token,
func
});
return token;
}
publish(topic, ...args) {
if (!this.topics[topic]) {
return false;
}
setTimeout(() => {
this.topics[topic].forEach(subscriber => {
subscriber.func(...args);
});
}, 0);
return true;
}
unsubscribe(token) {
for (const topic in this.topics) {
if (this.topics[topic]) {
this.topics[topic] = this.topics[topic].filter(
subscriber => subscriber.token !== token
);
}
}
return this;
}
}
// 使用
const pubsub = new PubSub();
// 订阅消息
const token = pubsub.subscribe('news', (data) => {
console.log('收到新闻:', data.title);
});
// 发布消息
pubsub.publish('news', {
title: '重大突破!',
content: '...'
});
// 取消订阅
pubsub.unsubscribe(token);
模块模式
模块模式使用闭包来封装私有状态和方法,只暴露公共接口。在ES6模块出现之前,这是JavaScript中实现封装的主要方式。
const UserModule = (function() {
// 私有变量和方法
const users = [];
let currentId = 0;
function generateId() {
return ++currentId;
}
function validateUser(user) {
return user.name && user.email;
}
// 公共接口
return {
addUser(user) {
if (!validateUser(user)) {
throw new Error('无效的用户数据');
}
const newUser = {
id: generateId(),
...user,
createdAt: new Date()
};
users.push(newUser);
return newUser;
},
getUser(id) {
return users.find(u => u.id === id);
},
getAllUsers() {
return [...users];
},
removeUser(id) {
const index = users.findIndex(u => u.id === id);
if (index > -1) {
return users.splice(index, 1)[0];
}
return null;
}
};
})();
// 使用
const user = UserModule.addUser({
name: '张三',
email: 'zhangsan@example.com'
});
策略模式
策略模式定义一系列算法,把它们一个个封装起来,并且使它们可以互相替换。这种模式让算法独立于使用它的客户而变化。
// 策略对象
const strategies = {
S: (salary) => salary * 4,
A: (salary) => salary * 3,
B: (salary) => salary * 2,
C: (salary) => salary * 1.5
};
// 上下文
class BonusCalculator {
constructor() {
this.strategy = null;
this.salary = 0;
}
setStrategy(strategy) {
this.strategy = strategy;
}
setSalary(salary) {
this.salary = salary;
}
calculate() {
if (!this.strategy) {
throw new Error('未设置策略');
}
return strategies[this.strategy](this.salary);
}
}
// 使用
const calculator = new BonusCalculator();
calculator.setSalary(10000);
calculator.setStrategy('S');
console.log(calculator.calculate()); // 40000
calculator.setStrategy('B');
console.log(calculator.calculate()); // 20000
装饰器模式
装饰器模式动态地给对象添加一些额外的职责。相比继承,装饰器模式更加灵活,可以在运行时决定添加什么功能。
// 基础组件
class Coffee {
cost() {
return 10;
}
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() + 3;
}
description() {
return this.coffee.description() + ' + 牛奶';
}
}
class SugarDecorator extends CoffeeDecorator {
cost() {
return this.coffee.cost() + 1;
}
description() {
return this.coffee.description() + ' + 糖';
}
}
class WhipDecorator extends CoffeeDecorator {
cost() {
return this.coffee.cost() + 5;
}
description() {
return this.coffee.description() + ' + 奶油';
}
}
// 使用
let coffee = new Coffee();
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);
console.log(coffee.description()); // 普通咖啡 + 牛奶 + 糖
console.log(coffee.cost()); // 14
最佳实践与注意事项
- 不要过度设计:简单的问题用简单的方案解决,设计模式是用来解决复杂问题的
- 理解场景再选模式:每个模式都有适用场景,生搬硬套只会增加复杂度
- 组合优于继承:在JavaScript中,组合和代理往往比继承更灵活
- 利用语言特性:JavaScript的闭包、高阶函数等特性可以实现更简洁的模式实现
- 保持单一职责:每个模块、类应该只有一个变化的理由
- 注重代码可读性:模式的目的是让代码更好维护,不是炫技
总结
设计模式是软件工程领域的宝贵财富,掌握它们能让你在面对复杂问题时游刃有余。但记住,模式是工具不是目的,代码的最终目标是解决问题、易于理解和维护。在实际开发中,根据具体场景选择合适的模式,而不是为了使用模式而使用模式。
JavaScript的灵活性让我们能够以多种方式实现这些模式,选择最适合团队和项目的方式才是关键。希望这篇文章能帮助你建立对设计模式的深入理解,在代码之路上越走越远。