一、设计模式是什么?
设计模式是在某种场合下对某个问题的一种解决方案。设计模式是通过概念总结出来的模版,总结出来的固定的东西。每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。
在前端开发中,设计模式是一种被广泛应用的思想。设计模式可以帮助开发者解决常见的问题,并提供可重用的解决方案。本文将会介绍前端常见的设计模式,并通过代码详解它们的实现。
二、设计原则
1. S – Single Responsibility Principle 单一职责原则
- 一个程序只做好一件事
- 如果功能过于复杂就拆分开,每个部分保持独立
2. O – OpenClosed Principle 开放/封闭原则
- 对扩展开放,对修改封闭
- 增加需求时,扩展新代码,而非修改已有代码
3. L – Liskov Substitution Principle 里氏替换原则
- 子类能覆盖父类
- 父类能出现的地方子类就能出现
4. I – Interface Segregation Principle 接口隔离原则
- 保持接口的单一独立
- 类似单一职责原则,这里更关注接口
5. D – Dependency Inversion Principle 依赖倒转原则
- 面向接口编程,依赖于抽象而不依赖于具
- 使用方只关注接口而不关注具体类的实现
三、设计模式的类型
1. 结构型模式(Structural Patterns):
通过识别系统中组件间的简单关系来简化系统的设计。
2. 创建型模式(Creational Patterns):
处理对象的创建,根据实际情况使用合适的方式创建对象。常规的对象创建方式可能会导致设计上的问题,或增加设计的复杂度。创建型模式通过以某种方式控制对象的创建来解决问题。
3. 行为型模式(Behavioral Patterns):
用于识别对象之间常见的交互模式并加以实现,如此,增加了这些交互的灵活性。
四、前端常见的8种设计模式
1、单例模式
单例模式是指一个类只能被实例化一次,并提供全局访问点。这个模式非常适合那些需要共享资源的场景。比如,在前端开发中,我们经常需要确保某些资源只被加载一次,而不是每次都重新加载。下面是一个使用单例模式的例子:
class Singleton { constructor() { if (typeof Singleton.instance === 'object') { return Singleton.instance; } this.name = 'Singleton'; Singleton.instance = this; return this; } } const instance1 = new Singleton(); const instance2 = new Singleton(); console.log(instance1 === instance2); // true
上述代码中,我们创建了一个 Singleton 类,该类只能被实例化一次。如果多次尝试实例化该类,将会返回同一个实例。这样就保证了某些资源只会被加载一次,从而提高性能。
2、观察者模式
观察者模式是指当一个对象状态改变时,它的所有依赖者都会收到通知并自动更新。这个模式非常适合那些需要实时更新用户界面的场景。下面是一个使用观察者模式的例子:
class ObserverList { constructor() { this.observerList = []; } add(observer) { return this.observerList.push(observer); } remove(observer) { this.observerList = this.observerList.filter((obs) => obs !== observer); } count() { return this.observerList.length; } get(i) { return this.observerList[i]; } } class Subject { constructor() { this.observers = new ObserverList(); } addObserver(observer) { this.observers.add(observer); } removeObserver(observer) { this.observers.remove(observer); } notify(context) { const observerCount = this.observers.count(); for (let i = 0; i < observerCount; i++) { this.observers.get(i).update(context); } } } class Observer { constructor() { this.update = () => {}; } } const subject = new Subject(); const observer1 = new Observer(); observer1.update = function (context) { console.log(`Observer 1: ${context}`); }; const observer2 = new Observer(); observer2.update = function (context) { console.log(`Observer 2: ${context}`); }; subject.addObserver(observer1); subject.addObserver(observer2); subject.notify('Hello World');
上述代码中,我们创建了一个 Subject 类和一个 Observer 类。Subject 类包含了一个观察者列表,并提供了添加、删除和通知观察者的方法。Observer 类则包含了一个更新方法,用于处理来自被观察者的通知。在主程序中,我们创建了两个观察者,并将它们添加到被观察者的观察者列表中。然后,我们通知被观察者,被观察者会自动通知所有观察者,并执行它们的更新方法。
3、工厂模式
工厂模式是指通过一个工厂类来创建其他类的实例。这个模式非常适合那些需要根据不同条件创建不同实例的场景。下面是一个使用工厂模式的例子:
class ProductA { constructor(name) { this.name = name; } operation() { console.log(`Product A (${this.name}) is working.`); } } class ProductB { constructor(name) { this.name = name; } operation() { console.log(`Product B (${this.name}) is working.`); } } class Factory { createProduct(type, name) { switch (type) { case 'A': return new ProductA(name); case 'B': return new ProductB(name); default: throw new Error('Invalid product type.'); } } } const factory = new Factory(); const productA1 = factory.createProduct('A', 'productA1'); const productA2 = factory.createProduct('A', 'productA2'); const productB1 = factory.createProduct('B', 'productB1'); const productB2 = factory.createProduct('B', 'productB2'); productA1.operation(); // Product A (productA1) is working. productA2.operation(); // Product A (productA2) is working. productB1.operation(); // Product B (productB1) is working. productB2.operation(); // Product B (productB2) is working.
上述代码中,我们创建了两个产品类 `ProductA` 和 `ProductB`,以及一个工厂类 `Factory`。工厂类提供了一个创建产品实例的方法 `createProduct`,该方法根据传入的参数来决定创建哪种产品实例。在主程序中,我们通过工厂类创建了四个不同的产品实例,并分别执行它们的操作方法。
4、装饰者模式
装饰者模式是指动态地给一个对象增加一些额外的功能。这个模式非常适合那些需要在运行时动态改变对象行为的场景。下面是一个使用装饰者模式的例子:
class Shape { draw() { } } class Circle extends Shape { draw() { console.log('Drawing a circle.'); } } class Rectangle extends Shape { draw() { console.log('Drawing a rectangle.'); } } class Decorator { constructor(shape) { this.shape = shape; } draw() { this.shape.draw(); } } class RedShapeDecorator extends Decorator { draw() { this.shape.draw(); this.setRedBorder(); } setRedBorder() { console.log('Setting red border.'); } } const circle = new Circle(); const rectangle = new Rectangle(); circle.draw(); // Drawing a circle. rectangle.draw(); // Drawing a rectangle. const redCircle = new RedShapeDecorator(new Circle()); const redRectangle = new RedShapeDecorator(new Rectangle()); redCircle.draw(); // Drawing a circle. Setting red border. redRectangle.draw(); // Drawing a rectangle. Setting red border.
上述代码中,我们创建了两个形状类 Circle 和 Rectangle,以及一个装饰者类 Decorator。装饰者类包含了一个形状对象,用于对其进行装饰。然后,我们创建了一个红色形状装饰者类 RedShapeDecorator,用于在形状周围添加一个红色边框。在主程序中,我们先执行原始形状的绘制方法,然后再使用红色装饰器对其进行装饰。
5、代理模式
代理模式是指使用一个代理对象来控制对另一个对象的访问。这个模式非常适合那些需要控制对某些敏感资源的访问的场景。下面是一个使用代理模式的例子:
class Image { constructor(url) { this.url = url; this.loadImage(); } loadImage() { console.log(`Loading image from ${this.url}`); } } class ProxyImage { constructor(url) { this.url = url; } loadImage() { if (!this.image) { this.image = new Image(this.url); } console.log(`Displaying cached image from ${this.url}`); } } const image1 = new Image('https://example.com/image1.jpg'); const proxyImage1 = new ProxyImage('https://example.com/image1.jpg'); proxyImage1.loadImage(); // Loading image from https://example.com/image1.jpg proxyImage1.loadImage(); // Displaying cached image from https://example.com/image1.jpg const image2 = new Image('https://example.com/image2.jpg'); const proxyImage2 = new ProxyImage('https://example.com/image2.jpg'); proxyImage2.loadImage(); // Loading image from https://example.com/image2.jpg proxyImage2.loadImage(); // Displaying cached image from https://example.com/image2.jpg
上述代码中,我们创建了一个图片类 `Image` 和一个代理图片类 `ProxyImage`。代理图片类包含了一个图片对象,用于控制对其加载和显示的访问。在主程序中,我们首先创建了一个真实的图片对象,并使用代理图片对象进行访问。第一次访问时,代理图片对象会加载并显示真实的图片;第二次访问时,代理图片对象直接从缓存中获取并显示图片。
6、适配器模式
适配器模式是指将不兼容接口的对象转化为兼容接口的对象。这个模式非常适合那些需要改变接口而不影响原有代码的场景。下面是一个使用适配器模式的例子:
class OldCalculator { operations(a, b, operation) { switch (operation) { case 'add': return a + b; case 'sub': return a - b; default: return NaN; } } } class NewCalculator { add(a, b) { return a + b; } sub(a, b) { return a - b; } } class CalculatorAdapter { constructor() { this.newCalculator = new NewCalculator(); } operations(a, b, operation) { switch (operation) { case 'add': return this.newCalculator.add(a, b); case 'sub': return this.newCalculator.sub(a, b); default: return NaN; } } } const oldCalculator = new OldCalculator(); console.log(oldCalculator.operations(10, 5, 'add')); // 15 const newCalculator = new NewCalculator(); console.log(newCalculator.add(10, 5)); // 15 const calculatorAdapter = new CalculatorAdapter(); console.log(calculatorAdapter.operations(10, 5, 'add')); // 15
上述代码中,我们创建了一个旧计算器类 OldCalculator 和一个新计算器类 NewCalculator。然后,我们创建了一个适配器类 CalculatorAdapter,该类包含了一个新计算器对象,用于将旧计算器的操作转化为新计算器的操作。在主程序中,我们分别使用旧计算器、新计算器和适配器进行加法运算,并得到了相同的结果。
7、命令模式
命令模式是指将请求封装成一个对象,并提供与执行该请求相关的所有信息。这个模式非常适合那些需要执行多个不同操作的场景。下面是一个使用命令模式的例子:
class Receiver { run() { console.log('Receiver is running.'); } } class Command { constructor(receiver) { this.receiver = receiver; } execute() {} } class StartCommand extends Command { execute() { this.receiver.run(); } } class Invoker { setCommand(command) { this.command = command; } executeCommand() { this.command.execute(); } } const receiver = new Receiver(); const startCommand = new StartCommand(receiver); const invoker = new Invoker(); invoker.setCommand(startCommand); invoker.executeCommand(); // Receiver is running.
上述代码中,我们创建了一个接收者类 Receiver 和一个命令基类 Command。然后,我们创建了一个具体命令类 StartCommand,该类继承自命令基类,并实现了执行方法 execute,用于启动接收者。最后,我们创建了一个调用者类 Invoker,该类包含了一个命令对象,并提供了执行命令的方法。在主程序中,我们创建了一个接收者对象、一个具体命令对象和一个调用者对象,并将具体命令对象设置为调用者对象的命令。然后,我们执行调用者对象的执行方法,该方法会调用具体命令对象的执行方法,从而启动接收者。这个例子比较简单,但命令模式可以应用于很多复杂的场景,例如撤销/恢复操作、事务管理等。
8、观察者模式
观察者模式是指在对象之间定义一种一对多的依赖关系,使得每当一个对象改变状态时,所有依赖于它的对象都会得到通知并自动更新。这个模式非常适合那些需要在应用中实现事件处理的场景。下面是一个使用观察者模式的例子:
class Subject { constructor() { this.observers = []; } addObserver(observer) { this.observers.push(observer); } removeObserver(observer) { const index = this.observers.indexOf(observer); if (index >= 0) { this.observers.splice(index, 1); } } notifyObservers() { for (const observer of this.observers) { observer.update(this); } } } class ConcreteSubject extends Subject { constructor(state) { super(); this.state = state; } getState() { return this.state; } setState(state) { this.state = state; this.notifyObservers(); } } class Observer { update() {} } class ConcreteObserver extends Observer { update(subject) { console.log(`The subject has changed to ${subject.getState()}.`); } } const subject = new ConcreteSubject('state1'); const observer1 = new ConcreteObserver(); const observer2 = new ConcreteObserver(); subject.addObserver(observer1); subject.addObserver(observer2); subject.setState('state2'); // The subject has changed to state2.
上述代码中,我们创建了一个主题基类 Subject 和一个具体主题类 ConcreteSubject。主题类包含了一个状态属性和一组观察者对象,并提供了方法以添加、删除和通知观察者。然后,我们创建了一个观察者基类 Observer 和一个具体观察者类 ConcreteObserver,该类继承自观察者基类,并实现了更新方法。在主程序中,我们创建了一个具体主题对象、两个具体观察者对象,并将观察者对象添加到主题对象中。然后,我们修改了主题对象的状态,并通过主题对象通知观察者对象进行更新操作。
以上就是八种常见的设计模式以及它们的应用场景和示例代码。当然,这只是冰山一角,还有很多其他的设计模式可以应用于不同的场景。熟悉各种设计模式并且能够灵活运用它们可以让你成为更优秀的开发者。