JavaScript设计模式是指在JavaScript编程中普遍应用的一系列经过验证的最佳实践和可重用的解决方案模板,它们用来解决在软件设计中频繁出现的问题,如对象的创建、职责分配、对象间通信以及系统架构等。
设计模式并不特指某个具体的代码片段,而是一种描述在特定上下文中如何组织程序结构和对象交互的通用指导原则。JavaScript设计模式涵盖了创建型模式(处理对象创建的方式)、结构型模式(关注对象或类的组合方式以形成更大的结构)和行为型模式(描述对象间的职责分配与协调机制)等三大类别,以及其他适用于JavaScript环境的特定模式。
简而言之,JavaScript设计模式是用来提升代码质量、增强代码可读性、提高代码复用性和可维护性的设计原则集合。
单例模式是一种确保在任何情况下一个类仅有一个实例,并提供全局访问点的设计模式。它主要用于控制对全局唯一资源的访问。
就好比一座城市里只有一座供水站,不论你需要从哪里取水,都只能通过这个供水站来获取,而且全市人民共用的是同一座供水站。
class Singleton {
static instance = null;
constructor() {
if (!Singleton.instance) {
Singleton.instance = this;
}
return Singleton.instance;
}
someSharedResource() {
// 实现共享资源的方法
}
}
const instance1 = new Singleton();
const instance2 = new Singleton();
console.log(instance1 === instance2); // 输出 true,表明它们是同一个实例
抽象工厂模式提供一个接口用于创建一系列相关或相互依赖的对象,而无需指定具体类。客户端使用此接口选择所需的产品族中的产品对象。
设想一个汽车工厂不仅能生产各种类型的车(如轿车、SUV等),还能生产配套的轮胎和内饰。客户只要告诉工厂要哪种类型的车,工厂就会相应地提供整套适合的汽车部件。
// 抽象工厂
class AbstractCarFactory {
createCar() {
throw new Error('抽象方法,需要子类实现');
}
createTires() {
throw new Error('抽象方法,需要子类实现');
}
createInterior() {
throw new Error('抽象方法,需要子类实现');
}
}
// 具体工厂
class LuxuryCarFactory extends AbstractCarFactory {
createCar() { return new LuxuryCar(); }
createTires() { return new PremiumTires(); }
createInterior() { return new LeatherInterior(); }
}
// 产品类
class Car {}
class LuxuryCar extends Car {}
class Tires {}
class PremiumTires extends Tires {}
class Interior {}
class LeatherInterior extends Interior {}
// 使用
const factory = new LuxuryCarFactory();
const car = factory.createCar();
const tires = factory.createTires();
const interior = factory.createInterior();
工厂模式定义了一个创建对象的接口,但让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
比如你走进一家甜品店,向服务员说想要一杯饮料,具体是什么饮料由服务员决定(可能是咖啡或果汁)。服务员就是这里的“工厂”,负责根据你的需求生产出具体的饮料产品。
class DrinkFactory {
createDrink(type) {
switch (type) {
case 'coffee':
return new Coffee();
case 'juice':
return new Juice();
default:
throw new Error('不支持的饮料类型');
}
}
}
class Coffee {}
class Juice {}
const factory = new DrinkFactory();
const coffee = factory.createDrink('coffee');
const juice = factory.createDrink('juice');
建造者模式将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。客户端不需要知道内部组件的具体构建细节。
就如同组装一台电脑,你可以选择不同配置的CPU、内存、硬盘等配件,最后由装机员按照你的配置清单来组装。组装过程(Builder)是固定的,但最终产出的电脑配置各异。
class ComputerBuilder {
constructor() {
this.computer = {
cpu: '',
memory: '',
hardDrive: ''
};
}
setCPU(cpu) {
this.computer.cpu = cpu;
return this;
}
setMemory(memory) {
this.computer.memory = memory;
return this;
}
setHardDrive(hardDrive) {
this.computer.hardDrive = hardDrive;
return this;
}
build() {
return this.computer;
}
}
class DesktopComputerBuilder extends ComputerBuilder {
// 可能会添加一些桌面电脑特有的配置方法
}
const builder = new DesktopComputerBuilder()
.setCPU('Intel Core i7')
.setMemory('16GB DDR4')
.setHardDrive('1TB SSD');
const computer = builder.build();
console.log(computer);
原型模式是一种复制已有对象作为新对象的方式,通过克隆原型对象并对其稍作修改来创建新的对象,而不是重新初始化一个新对象。
就如同制作陶艺,工匠可以根据一个基础模型(原型)快速复制出相似的作品,然后再针对每个复制品进行个性化装饰。
function PrototypeObj(name) {
this.name = name;
}
// 添加一个clone方法到原型上
PrototypeObj.prototype.clone = function() {
let clone = Object.create(this);
clone.name = this.name + '_copy';
return clone;
};
let original = new PrototypeObj('Original');
let copy = original.clone();
console.log(copy.name); // 输出 "Original_copy"
适配器模式将一个类的接口转换为客户希望的另一个接口,使原本不兼容的接口能协同工作。主要应用于当系统需要使用现有的类,但是接口不符合需求时。
就像电源插头转换器,将不同标准的插头转为适应目的地插座的标准。
// 假设我们有一个现有接口
class Adaptee {
specificRequest() {
console.log('执行特殊请求');
}
}
// 目标接口
interface TargetInterface {
request(): void;
}
// 适配器类
class Adapter implements TargetInterface {
private adaptee: Adaptee;
constructor(adaptee: Adaptee) {
this.adaptee = adaptee;
}
request() {
this.adaptee.specificRequest();
}
}
// 使用
let adaptee = new Adaptee();
let adapter: TargetInterface = new Adapter(adaptee);
adapter.request(); // 输出 "执行特殊请求"
桥接模式将抽象部分与其实现部分分离,使它们可以独立变化。它主要用于解耦抽象和实现,从而让它们可以独立演化。
比如电脑品牌和操作系统是两个维度的变化,桥接模式就是让电脑品牌可以选择不同的操作系统,二者互不影响。
// 抽象部分
abstract class DrawingAPI {
abstract drawCircle(radius: number, x: number, y: number): void;
}
// 具体实现部分
class DrawingAPI1 extends DrawingAPI {
drawCircle(radius, x, y) {
console.log(`Drawing circle with radius ${radius}, at (${x}, ${y}) using API 1.`);
}
}
class DrawingAPI2 extends DrawingAPI {
drawCircle(radius, x, y) {
console.log(`Drawing circle with radius ${radius}, at (${x}, ${y}) using API 2.`);
}
// 结构部分
class Shape {
protected drawingAPI: DrawingAPI;
constructor(drawingAPI: DrawingAPI) {
this.drawingAPI = drawingAPI;
}
setAPI(drawingAPI: DrawingAPI) {
this.drawingAPI = drawingAPI;
}
abstract draw(): void;
}
// 结构与实现结合
class CircleShape extends Shape {
constructor(drawingAPI: DrawingAPI) {
super(drawingAPI);
}
draw() {
this.drawingAPI.drawCircle(10, 50, 50);
}
}
// 使用
let shape = new CircleShape(new DrawingAPI1());
shape.draw(); // 输出 "Drawing circle with radius 10, at (50, 50) using API 1."
shape.setAPI(new DrawingAPI2());
shape.draw(); // 输出 "Drawing circle with radius 10, at (50, 50) using API 2."
装饰模式动态地给一个对象添加一些额外的职责,提供比继承更有弹性的替代方案来扩展对象的功能。
比如咖啡基础款可以加糖、加奶、加香草等,每一种装饰都是在原有基础上增加新特性,而不是每次都创建新的咖啡品种。
// 基础组件
class Coffee {
cost(): number {
return 10;
}
description(): string {
return 'Coffee';
}
}
// 装饰者
abstract class CoffeeDecorator implements Coffee {
protected coffee: Coffee;
constructor(coffee: Coffee) {
this.coffee = coffee;
}
cost(): number {
return this.coffee.cost();
}
description(): string {
return this.coffee.description();
}
}
class Milk extends CoffeeDecorator {
constructor(coffee: Coffee) {
super(coffee);
}
cost(): number {
return super.cost() + 2; // 加入牛奶的成本
}
description(): string {
return super.description() + ', Milk'; // 描述中加入牛奶
}
}
// 使用
let coffee = new Coffee();
console.log(coffee.cost()); // 输出:10
console.log(coffee.description()); // 输出:"Coffee"
let milkCoffee = new Milk(coffee);
console.log(
组合模式允许你将对象组合成树形结构来表现“整体-部分”层次结构,并且用户对单个对象和组合对象的使用具有一致性。
就像文件夹和文件的关系,文件夹里可以包含文件和子文件夹,无论操作单个文件还是整个文件夹,都采用相同的方式。
class Component {
operation(): string {
return '默认组件操作';
}
add(component: Component): void {}
remove(component: Component): void {}
}
class Leaf extends Component {
operation(): string {
return '叶子节点操作';
}
}
class Composite extends Component {
private children: Component[] = [];
add(component: Component): void {
this.children.push(component);
}
remove(component: Component): void {
const index = this.children.indexOf(component);
if (index > -1) {
this.children.splice(index, 1);
}
}
operation(): string {
let result = '';
for (const child of this.children) {
result += child.operation();
}
return `复合组件操作: ${result}`;
}
}
// 使用
let leaf = new Leaf();
console.log(leaf.operation()); // 输出:"叶子节点操作"
let composite = new Composite();
composite.add(leaf);
console.log(composite.operation()); // 输出:"复合组件操作: 叶子节点操作"
外观模式为子系统中的一组接口提供一个统一的高层接口,简化了该子系统的使用。
就如同电视机遥控器,它隐藏了电视机内部复杂的电路控制逻辑,只提供几个简单的按钮供用户操作。
class SubSystemOne {
method1(): void {
console.log('子系统1的方法1被调用');
}
}
class SubSystemTwo {
method2(): void {
console.log('子系统2的方法2被调用');
}
}
class Facade {
private subsystemOne: SubSystemOne;
private subsystemTwo: SubSystemTwo;
constructor() {
this.subsystemOne = new SubSystemOne();
this.subsystemTwo = new SubSystemTwo();
}
facadeMethod():
享元模式运用共享技术有效支持大量细粒度的对象,通过共享已存在的同类对象来大幅度减少创建新对象的数量,从而节省系统资源。
例如一个大型游戏中大量的小兵角色,他们虽然个体差异不大,但数量众多,可以通过共享一部分数据来减少内存占用。
class Flyweight {
private intrinsicState: string;
constructor(intrinsicState: string) {
this.intrinsicState = intrinsicState;
}
operation(extrinsicState: string): string {
return `${this.intrinsicState}, ${extrinsicState}`;
}
}
class FlyweightFactory {
private flyweights: Map = new Map();
getFlyweight(intrinsicState: string): Flyweight {
let flyweight = this.flyweights.get(intrinsicState);
if (!flyweight) {
flyweight = new Flyweight(intrinsicState);
this.flyweights.set(intrinsicState, flyweight);
}
return flyweight;
}
}
// 使用
let factory = new FlyweightFactory();
let flyweight1 = factory.getFlyweight('TypeA');
console.log(flyweight1.operation('Instance1')); // 输出:"TypeA, Instance1"
let flyweight2 = factory.getFlyweight('TypeA');
console.log(flyweight2.operation('Instance2')); // 输出:"TypeA, Instance2"
// 注意这里返回的是同一个享元对象,节省了内存
代理模式为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不能或者不应该直接引用另一个对象,代理对象作为中间人起到中介作用。
类似于明星经纪人,粉丝们通常不会直接接触明星,而是通过经纪人进行沟通和安排事务。
class RealSubject {
request(): string {
return '真实的请求响应';
}
}
class Proxy {
private realSubject: RealSubject;
constructor(realSubject: RealSubject) {
this.realSubject = realSubject;
}
request(): string {
if (this.checkAccess()) {
return this.realSubject.request();
} else {
throw new Error('无权访问');
}
}
private checkAccess(): boolean {
// 这里模拟检查权限的过程
return true; // 假设当前有访问权限
}
}
// 使用
let realSubject = new RealSubject();
let proxy = new Proxy(realSubject);
try {
console.log(proxy.request()); // 输出:"真实的请求响应"
} catch (error) {
console.error(error.message);
}
请注意,由于JavaScript不支持接口(interface),在适配器模式和享元模式的例子中,我用的是类来模拟接口。在实际JavaScript项目中,通常我们会通过约定的方式来实现接口约束。
在抽象类中定义一个基本算法的框架,而将一些步骤延迟到子类中实现。它允许子类在不修改整体算法结构的情况下重新定义某些步骤。
就像烹饪菜谱,给出了做一道菜的基本流程,但具体每个步骤的实现(如炒菜调料的选择)由各个具体的菜品子类决定。
class AbstractClass {
templateMethod() {
this.baseOperation1();
this.optionalOperation1(); // 子类可覆盖此方法
this.requiredOperation();
this.optionalOperation2(); // 子类可覆盖此方法
}
baseOperation1() {
console.log('基本操作1');
}
requiredOperation() {
console.log('必须执行的操作');
}
optionalOperation1() { /* 子类可覆盖 */ }
optionalOperation2() { /* 子类可覆盖 */ }
}
class ConcreteClass extends AbstractClass {
optionalOperation1() {
console.log('具体类实现的操作1');
}
optionalOperation2() {
console.log('具体类实现的操作2');
}
}
let concrete = new ConcreteClass();
concrete.templateMethod();
将一个请求封装为一个对象,使得可以用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。
如同遥控器上的按键,每一个按键代表一个命令,按下按键就能执行相应的操作,还能实现撤销操作等功能。
class Receiver {
executeCommand() {
console.log('接收者执行命令');
}
}
class Command {
constructor(receiver: Receiver) {
this.receiver = receiver;
}
execute() {
this.receiver.executeCommand();
}
undo() {
console.log('撤销命令');
}
}
class Invoker {
command: Command | null = null;
setCommand(command: Command) {
this.command = command;
}
invoke() {
if (this.command) {
this.command.execute();
}
}
undoInvoke() {
if (this.command) {
this.command.undo();
}
}
}
let receiver = new Receiver();
let command = new Command(receiver);
let invoker = new Invoker();
invoker.setCommand(command);
invoker.invoke(); // 输出 "接收者执行命令"
invoker.undoInvoke(); // 输出 "撤销命令"
提供一种方法顺序访问聚合对象的各个元素,而又不暴露其底层表示。迭代器模式定义了一个访问一系列元素的接口,各元素之间关系紧密但又不需要暴露细节。
就像看书目录,可以按照一定的顺序一页页翻看,而无需了解书的具体装订方式。
class Collection {
constructor(items = []) {
this.items = items;
}
[Symbol.iterator]() {
let index = 0;
let collection = this.items;
return {
next: () => {
if (index < collection.length) {
return { value: collection[index++], done: false };
} else {
return { done: true };
}
}
};
}
}
let collection = new Collection(['Apple', 'Banana', 'Cherry']);
for (let item of collection) {
console.log(item); // 输出 "Apple", "Banana", "Cherry"
}
// 或者使用数组的内置迭代器
let fruits = ['Apple', 'Banana', 'Cherry'];
for (let fruit of fruits) {
console.log(fruit); // 输出 "Apple", "Banana", "Cherry"
}
定义了对象之间的依赖关系,一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。
像订阅新闻一样,当你订阅了某个主题后,每当有新的新闻更新时,你就会收到通知。
class Subject {
constructor() {
this.observers = [];
}
subscribe(observer) {
this.observers.push(observer);
}
unsubscribe(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 {
update(data) {
console.log('Received data:', data);
}
}
let subject = new Subject();
let observer1 = new Observer();
let observer2 = new Observer();
subject.subscribe(observer1);
subject.subscribe(observer2);
subject.notify('New Data'); // 输出两次 "Received data: New Data"
subject.unsubscribe(observer1);
subject.notify('Another Data'); // 输出一次 "Received data: Another Data"
封装一些作用于某种数据结构中的各种元素的操作,它可以在不改变元素类的前提下定义作用于这些元素的新操作。
类似检查员去多个部门审核,各部门只需提供接受检查的接口,而无需关心检查的具体规则,检查员则携带不同规则去访问各部门。
// 定义元素接口
class Element {
accept(visitor) {
throw new Error('This method should be implemented in subclasses');
}
}
// 具体元素类
class ConcreteElementA extends Element {
accept(visitor) {
visitor.visitConcreteElementA(this);
}
}
class ConcreteElementB extends Element {
accept(visitor) {
visitor.visitConcreteElementB(this);
}
}
// 定义访问者接口
interface Visitor {
visitConcreteElementA(element);
visitConcreteElementB(element);
}
// 具体访问者类
class ConcreteVisitor implements Visitor {
visitConcreteElementA(element) {
console.log('Visited ConcreteElementA');
}
visitConcreteElementB(element) {
console.log('Visited ConcreteElementB');
}
}
let elementA = new ConcreteElementA();
let elementB = new ConcreteElementB();
let visitor = new ConcreteVisitor();
elementA.accept(visitor); // 输出 "Visited ConcreteElementA"
elementB.accept(visitor); // 输出 "Visited ConcreteElementB"
定义一个中介对象来封装一系列的对象交互,使各对象不需要显式地相互引用,从而降低耦合度,同时使得系统易于扩展。
就像公司内部员工有问题不直接相互联系,而是通过人事部门作为中介进行协调,这样避免了员工间的直接依赖关系。
class Mediator {
constructor() {
this.colleagues = {};
}
register(name, colleague) {
this.colleagues[name] = colleague;
colleague.setMediator(this);
}
send(message, sender) {
for (const key in this.colleagues) {
if (key !== sender) {
this.colleagues[key].receive(message);
}
}
}
}
class Colleague {
constructor(name) {
this.name = name;
this.mediator = null;
}
setMediator(mediator) {
this.mediator = mediator;
}
receive(message) {
console.log(`${this.name} received message from mediator: ${message}`);
}
sendMessage(message) {
this.mediator.send(message, this.name);
}
}
let mediator = new Mediator();
let colleague1 = new Colleague('Colleague1');
let colleague2 = new Colleague('Colleague2');
mediator.register('Colleague1', colleague1);
mediator.register('Colleague2', colleague2);
colleague1.sendMessage('Hello from Colleague1'); // 输出 "Colleague2 received message from mediator: Hello from Colleague1"
在不破坏封装性的前提下,捕获一个对象的内部状态以便稍后恢复。这种模式主要用于数据备份和还原操作,防止外部对象随意修改内部状态。
就像游戏存档,你可以随时保存游戏进度并在任何时候恢复到之前的状态。
class Originator {
constructor(state) {
this.state = state;
}
getState() {
return this.state;
}
setState(state) {
this.state = state;
}
createMemento() {
return { state: this.getState() }; // 创建备忘录对象
}
restoreFromMemento(memento) {
this.setState(memento.state); // 从备忘录恢复状态
}
}
class Caretaker {
constructor() {
this.mementos = [];
}
addMemento(memento) {
this.mementos.push(memento);
}
getMemento(index) {
return this.mementos[index];
}
}
let originator = new Originator('Initial State');
let caretaker = new Caretaker();
caretaker.addMemento(originator.createMemento()); // 存档
originator.setState('New State');
console.log(originator.getState()); // 输出 "New State"
originator.restoreFromMemento(caretaker.getMemento(0));
给定一门语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
就像是编程语言的编译器或解释器,它解析程序员写的代码并执行相应操作。
class Expression {
interpret(context) {
throw new Error('Subclasses must implement interpret().');
}
}
class TerminalExpression extends Expression {
interpret(context) {
// 根据具体上下文解释终结符表达式
}
}
class NonTerminalExpression extends Expression {
interpret(context) {
// 根据具体上下文解释非终结符表达式,可能包含子表达式的解释
}
}
// 上下文对象
class Context {}
// 使用解释器
let context = new Context();
let expression = new TerminalExpression(); // 或 NonTerminalExpression
expression.interpret(context);
允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
就像交通信号灯,红绿黄三种状态决定了不同的行为表现。
class State {
constructor(machine) {
this.machine = machine;
}
handle() {
throw new Error('Subclasses must implement handle().');
}
}
class RedLightState extends State {
handle() {
console.log('Changing to Green Light State.');
this.machine.setState(this.machine.greenLightState);
}
}
class GreenLightState extends State {
handle() {
console.log('Changing to Yellow Light State.');
this.machine.setState(this.machine.yellowLightState);
}
}
class YellowLightState extends State {
handle() {
console.log('Changing to Red Light State.');
this.machine.setState(this.machine.redLightState);
}
}
class TrafficLightMachine {
constructor() {
this.currentState = this.redLightState;
}
setState(newState) {
this.currentState = newState;
}
changeLight() {
this.currentState.handle();
}
get redLightState() {
return new RedLightState(this);
}
get greenLightState() {
return new GreenLightState(this);
}
get yellowLightState() {
return new YellowLightState(this);
}
}
let trafficLight = new TrafficLightMachine();
trafficLight.changeLight(); // 输出 "Changing to Green Light State."
trafficLight.changeLight(); // 输出 "Changing to Yellow Light State."
trafficLight.changeLight(); // 输出 "Changing to Red Light State."
定义一系列算法,把它们一个个封装起来,并且使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户。
如同不同的折扣计算策略,可以灵活切换,不影响使用折扣策略的购物车系统。
class Strategy {
calculatePrice(price) {
throw new Error('Subclasses must implement calculatePrice().');
}
}
class NormalStrategy extends Strategy {
calculatePrice(price) {
return price;
}
}
class DiscountStrategy extends Strategy {
constructor(discountRate) {
super();
this.discountRate = discountRate;
}
calculatePrice(price) {
return price * (1 - this.discountRate);
}
}
class ShoppingCart {
constructor(strategy) {
this.strategy = strategy;
}
setStrategy(strategy) {
this.strategy = strategy;
}
calculateTotalPrice(items) {
let totalPrice = 0;
for (const item of items) {
totalPrice += this.strategy.calculatePrice(item.price);
}
return totalPrice;
}
}
let normalShoppingCart = new ShoppingCart(new NormalStrategy());
let discountedShoppingCart = new ShoppingCart(new DiscountStrategy(0.1));
let items = [{price: 100}, {price: 200}, {price: 300}];
console.log(normalShoppingCart.calculateTotalPrice(items)); // 输出 600
discountedShoppingCart.setStrategy(new DiscountStrategy(0.2));
console.log(discountedShoppingCart.calculateTotalPrice(items)); // 输出 480
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。
就像公司的请假审批流程,员工提交请假申请后,申请会按照经理、总监、总经理的顺序逐级审批,直到某一级别负责人批准或拒绝为止。
class Approver {
constructor(successor) {
this.successor = successor;
}
processRequest(request) {
if (this.canApprove(request)) {
console.log(`${this.name} approved the request.`);
} else if (this.successor) {
this.successor.processRequest(request);
} else {
console.log('No one can approve this request.');
}
}
canApprove(request) {
throw new Error('Subclasses must implement canApprove().');
}
}
class Manager extends Approver {
constructor(successor) {
super(successor);
this.name = 'Manager';
}
canApprove(request) {
return request <= 5; // 经理只能批准5天内的请假申请
}
}
class Director extends Approver {
constructor(successor) {
super(successor);
this.name = 'Director';
}
canApprove(request) {
return request <= 10 && request > 5; // 总监只能批准5-10天内的请假申请
}
}
class CEO extends Approver {
constructor() {
super(null);
this.name = 'CEO';
}
canApprove(request) {
return request <= 15 && request > 10; // 总裁可以批准10-15天内的请假申请
}
}
let ceo = new CEO();
let director = new Director(ceo);
let manager = new Manager(director);
manager.processRequest(3); // 输出 "Manager approved the request."
manager.processRequest(7); // 输出 "Director approved the request."
manager.processRequest(12); // 输出 "CEO approved the request."
manager.processRequest(18); // 输出 "No one can approve this request."