各位听众朋友们,大家好!我是今天的讲师,咱们今天来聊聊 JavaScript 中的几个设计模式,它们就像武林秘籍一样,掌握了能让你写出更优雅、更易维护的代码。今天我们要讲的就是单例模式、工厂模式和观察者模式这三个。
首先,咱们来聊聊单例模式 (Singleton Pattern)
单例模式,顾名思义,就是确保一个类只有一个实例,并提供一个全局访问点。你可以把它想象成你的电脑里只有一个回收站,或者说一个国家只有一个总统。
实现方式:
实现单例模式的关键在于:
- 私有化构造函数: 防止外部直接
new
出新的实例。 - 静态方法或属性: 提供一个全局访问点来获取唯一的实例。
下面是几种常见的 JavaScript 单例模式实现方式:
版本一:简单的闭包实现
var Singleton = (function() {
var instance;
function createInstance() {
var object = new Object("我是唯一的实例");
return object;
}
return {
getInstance: function() {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();
var instance1 = Singleton.getInstance();
var instance2 = Singleton.getInstance();
console.log(instance1 === instance2); // true
解释一下:
- 我们使用立即执行函数 (IIFE) 创建了一个闭包,
instance
变量被保存在闭包中。 createInstance
函数负责创建实例。getInstance
函数是全局访问点,它检查instance
是否存在,如果不存在则创建,否则直接返回已存在的实例。
版本二:利用静态属性
var Singleton = (function() {
var instance = null;
function Singleton() {
if (instance) {
return instance;
}
this.data = "我是唯一的实例";
instance = this; // 关键的一步,将this赋值给instance
return instance;
}
return Singleton;
})();
var instance1 = new Singleton();
var instance2 = new Singleton();
console.log(instance1 === instance2); // true
console.log(instance1.data); // "我是唯一的实例"
这个版本稍微复杂一点,但更接近传统的面向对象实现:
instance
变量还是用来保存唯一的实例。- 构造函数
Singleton
检查instance
是否已经存在,如果存在则直接返回instance
。 - 如果
instance
不存在,则创建实例,并将this
(新创建的实例) 赋值给instance
。
版本三:ES6 Class 实现
class Singleton {
constructor() {
if (!Singleton.instance) {
this.data = "我是唯一的实例";
Singleton.instance = this;
}
return Singleton.instance;
}
}
const instance1 = new Singleton();
const instance2 = new Singleton();
console.log(instance1 === instance2); // true
console.log(instance1.data); // "我是唯一的实例"
ES6 的 class
语法让单例模式的实现更加简洁:
- 我们使用静态属性
Singleton.instance
来保存唯一的实例。 - 构造函数
constructor
检查Singleton.instance
是否已经存在,如果存在则直接返回Singleton.instance
。 - 如果
Singleton.instance
不存在,则创建实例,并将this
赋值给Singleton.instance
。
应用场景:
- 全局缓存: 例如,缓存用户配置信息、应用配置信息等。
- 线程池: 确保只有一个线程池实例来管理线程资源。
- 数据库连接池: 确保只有一个数据库连接池实例来管理数据库连接。
- 配置管理器: 确保只有一个配置管理器实例来读取和管理配置信息。
总结:
单例模式的核心思想是控制实例的创建,确保只有一个实例存在。选择哪种实现方式取决于你的代码风格和需求。
接下来,咱们聊聊工厂模式 (Factory Pattern)
工厂模式,简单来说,就是用一个工厂函数或类来创建对象,而不用直接 new
关键字。你可以把它想象成一个汽车工厂,你只需要告诉工厂你需要什么类型的汽车(比如轿车、SUV),工厂就会帮你生产出来,而你不需要关心汽车的具体制造过程。
实现方式:
工厂模式主要分为三种:
- 简单工厂模式 (Simple Factory Pattern)
- 工厂方法模式 (Factory Method Pattern)
- 抽象工厂模式 (Abstract Factory Pattern)
咱们先从最简单的开始:
简单工厂模式:
// 产品接口 (可选)
class Product {
constructor(name) {
this.name = name;
}
operation() {
console.log("我是 " + this.name + " 的操作");
}
}
// 具体产品 A
class ConcreteProductA extends Product {
constructor() {
super("产品 A");
}
}
// 具体产品 B
class ConcreteProductB extends Product {
constructor() {
super("产品 B");
}
}
// 简单工厂
class SimpleFactory {
static createProduct(type) {
switch (type) {
case "A":
return new ConcreteProductA();
case "B":
return new ConcreteProductB();
default:
throw new Error("不支持的产品类型");
}
}
}
// 使用
const productA = SimpleFactory.createProduct("A");
productA.operation(); // 输出: 我是 产品 A 的操作
const productB = SimpleFactory.createProduct("B");
productB.operation(); // 输出: 我是 产品 B 的操作
解释一下:
Product
是一个可选的产品接口,定义了产品的通用行为。ConcreteProductA
和ConcreteProductB
是具体的产品类,实现了Product
接口。SimpleFactory
是简单工厂类,它根据传入的类型来创建不同的产品。
简单工厂模式的优点是简单易懂,但缺点是当需要添加新的产品时,需要修改工厂类的代码,违反了开闭原则(对扩展开放,对修改关闭)。
工厂方法模式:
// 产品接口 (可选)
class Product {
constructor(name) {
this.name = name;
}
operation() {
console.log("我是 " + this.name + " 的操作");
}
}
// 具体产品 A
class ConcreteProductA extends Product {
constructor() {
super("产品 A");
}
}
// 具体产品 B
class ConcreteProductB extends Product {
constructor() {
super("产品 B");
}
}
// 抽象工厂
class Factory {
createProduct() {
throw new Error("必须实现 createProduct 方法");
}
}
// 具体工厂 A
class ConcreteFactoryA extends Factory {
createProduct() {
return new ConcreteProductA();
}
}
// 具体工厂 B
class ConcreteFactoryB extends Factory {
createProduct() {
return new ConcreteProductB();
}
}
// 使用
const factoryA = new ConcreteFactoryA();
const productA = factoryA.createProduct();
productA.operation(); // 输出: 我是 产品 A 的操作
const factoryB = new ConcreteFactoryB();
const productB = factoryB.createProduct();
productB.operation(); // 输出: 我是 产品 B 的操作
解释一下:
Product
和ConcreteProductA/B
的定义与简单工厂模式相同。Factory
是抽象工厂类,定义了创建产品的接口。ConcreteFactoryA
和ConcreteFactoryB
是具体工厂类,实现了Factory
接口,分别创建不同的产品。
工厂方法模式的优点是符合开闭原则,当需要添加新的产品时,只需要添加新的工厂类即可,不需要修改已有的代码。但缺点是类的数量会增加。
抽象工厂模式:
抽象工厂模式是工厂方法模式的进一步抽象,它用于创建一组相关的对象。你可以把它想象成一个家具工厂,它可以生产沙发、桌子、椅子等一系列相关的产品。
// 抽象产品 A
class AbstractProductA {
operationA() {
throw new Error("必须实现 operationA 方法");
}
}
// 抽象产品 B
class AbstractProductB {
operationB() {
throw new Error("必须实现 operationB 方法");
}
}
// 具体产品 A1
class ConcreteProductA1 extends AbstractProductA {
operationA() {
console.log("我是 A1 的 operationA");
}
}
// 具体产品 A2
class ConcreteProductA2 extends AbstractProductA {
operationA() {
console.log("我是 A2 的 operationA");
}
}
// 具体产品 B1
class ConcreteProductB1 extends AbstractProductB {
operationB() {
console.log("我是 B1 的 operationB");
}
}
// 具体产品 B2
class ConcreteProductB2 extends AbstractProductB {
operationB() {
console.log("我是 B2 的 operationB");
}
}
// 抽象工厂
class AbstractFactory {
createProductA() {
throw new Error("必须实现 createProductA 方法");
}
createProductB() {
throw new Error("必须实现 createProductB 方法");
}
}
// 具体工厂 1
class ConcreteFactory1 extends AbstractFactory {
createProductA() {
return new ConcreteProductA1();
}
createProductB() {
return new ConcreteProductB1();
}
}
// 具体工厂 2
class ConcreteFactory2 extends AbstractFactory {
createProductA() {
return new ConcreteProductA2();
}
createProductB() {
return new ConcreteProductB2();
}
}
// 使用
const factory1 = new ConcreteFactory1();
const productA1 = factory1.createProductA();
const productB1 = factory1.createProductB();
productA1.operationA(); // 输出: 我是 A1 的 operationA
productB1.operationB(); // 输出: 我是 B1 的 operationB
const factory2 = new ConcreteFactory2();
const productA2 = factory2.createProductA();
const productB2 = factory2.createProductB();
productA2.operationA(); // 输出: 我是 A2 的 operationA
productB2.operationB(); // 输出: 我是 B2 的 operationB
解释一下:
AbstractProductA
和AbstractProductB
是抽象产品接口,定义了产品的通用行为。ConcreteProductA1/A2
和ConcreteProductB1/B2
是具体的产品类,实现了抽象产品接口。AbstractFactory
是抽象工厂类,定义了创建一组相关产品的接口。ConcreteFactory1
和ConcreteFactory2
是具体工厂类,实现了AbstractFactory
接口,分别创建不同的产品组。
抽象工厂模式的优点是可以确保创建的产品是一致的,但缺点是类的数量会更多,更复杂。
应用场景:
- 创建不同类型的 UI 组件: 例如,根据不同的平台(Web、Android、iOS)创建不同的按钮、输入框等。
- 创建不同风格的主题: 例如,根据不同的主题(Light、Dark)创建不同的颜色、字体等。
- 创建不同数据库的连接: 例如,根据不同的数据库类型(MySQL、PostgreSQL、MongoDB)创建不同的连接对象。
总结:
工厂模式的核心思想是将对象的创建过程封装起来,使客户端不需要关心对象的具体创建过程。选择哪种工厂模式取决于你的代码复杂度和需求。
模式 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
简单工厂 | 简单易懂 | 违反开闭原则,添加新产品需要修改工厂类代码 | 创建对象逻辑简单,产品类型较少 |
工厂方法 | 符合开闭原则,添加新产品不需要修改已有代码 | 类数量增加 | 创建对象逻辑复杂,需要灵活扩展产品类型 |
抽象工厂 | 可以确保创建的产品是一致的 | 类数量更多,更复杂 | 创建一组相关的对象,需要确保产品的一致性 |
最后,咱们来聊聊观察者模式 (Observer Pattern)
观察者模式,也叫做发布-订阅模式 (Publish-Subscribe Pattern)。你可以把它想象成订阅报纸,你只需要订阅你感兴趣的报纸,当报纸更新时,你就会收到通知。
实现方式:
观察者模式主要有两个角色:
- 主题 (Subject): 也叫做发布者 (Publisher),它维护一个观察者列表,并负责通知观察者。
- 观察者 (Observer): 也叫做订阅者 (Subscriber),它注册到主题,并在主题状态改变时收到通知。
下面是一个简单的 JavaScript 观察者模式实现:
// 主题 (发布者)
class Subject {
constructor() {
this.observers = [];
}
subscribe(observer) {
this.observers.push(observer);
}
unsubscribe(observer) {
this.observers = this.observers.filter(obs => obs !== 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("主题状态改变了!");
// 输出:
// 观察者 1 收到通知:主题状态改变了!
// 观察者 2 收到通知:主题状态改变了!
subject.unsubscribe(observer1);
subject.notify("主题状态又改变了!");
// 输出:
// 观察者 2 收到通知:主题状态又改变了!
解释一下:
Subject
类维护了一个observers
数组,用于存储观察者。subscribe
方法用于添加观察者到observers
数组。unsubscribe
方法用于从observers
数组中移除观察者。notify
方法用于通知所有观察者,它会遍历observers
数组,并调用每个观察者的update
方法。Observer
类定义了update
方法,用于处理主题的通知。
应用场景:
- 事件处理: 例如,DOM 事件、自定义事件等。
- 消息队列: 例如,RabbitMQ、Kafka 等。
- 数据绑定: 例如,Vue.js、React 等框架中的数据绑定机制。
- 实时通信: 例如,WebSocket、Socket.IO 等。
总结:
观察者模式的核心思想是解耦主题和观察者,使它们可以独立变化。主题不需要知道观察者的具体实现,只需要知道它们实现了 update
方法即可。
JavaScript 中的内置支持:
JavaScript 提供了 EventTarget
接口和 dispatchEvent
方法,可以方便地实现观察者模式。
// 创建一个自定义事件
const myEvent = new Event('my-custom-event');
// 创建一个 EventTarget 对象
const target = new EventTarget();
// 添加一个事件监听器
target.addEventListener('my-custom-event', function(event) {
console.log('事件被触发了!', event);
});
// 触发事件
target.dispatchEvent(myEvent); // 输出: 事件被触发了! [object Event]
最后的总结
这三种设计模式只是冰山一角,还有很多其他的设计模式可以帮助你写出更好的代码。关键是理解这些模式背后的思想,并根据实际情况灵活运用。 学习设计模式不是死记硬背,而是理解它们的本质,并在合适的场景下应用它们。
希望今天的讲座对大家有所帮助!谢谢大家!