JS的设计模式(23种)
JavaScript设计模式是指在JavaScript编程中普遍应用的一系列经过验证的最佳实践和可重用的解决方案模板,它们用来解决在软件设计中频繁出现的问题,如对象的创建、职责分配、对象间通信以及系统架构等。
设计模式并不特指某个具体的代码片段,而是一种描述在特定上下文中如何组织程序结构和对象交互的通用指导原则。JavaScript设计模式涵盖了创建型模式(处理对象创建的方式)、结构型模式(关注对象或类的组合方式以形成更大的结构)和行为型模式(描述对象间的职责分配与协调机制)等三大类别,以及其他适用于JavaScript环境的特定模式。
简而言之,JavaScript设计模式是用来提升代码质量、增强代码可读性、提高代码复用性和可维护性的设计原则集合。
创建型模式
1. 单例模式(Singleton)
专业解释:
单例模式是一种确保在任何情况下一个类仅有一个实例,并提供全局访问点的设计模式。它主要用于控制对全局唯一资源的访问。
通俗理解:
就好比一座城市里只有一座供水站,不论你需要从哪里取水,都只能通过这个供水站来获取,而且全市人民共用的是同一座供水站。
示例代码(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,表明它们是同一个实例
2. 抽象工厂模式(Abstract Factory)
专业解释:
抽象工厂模式提供一个接口用于创建一系列相关或相互依赖的对象,而无需指定具体类。客户端使用此接口选择所需的产品族中的产品对象。
通俗理解:
设想一个汽车工厂不仅能生产各种类型的车(如轿车、SUV等),还能生产配套的轮胎和内饰。客户只要告诉工厂要哪种类型的车,工厂就会相应地提供整套适合的汽车部件。
示例代码(JavaScript):
// 抽象工厂
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();
3. 工厂模式(Factory Method)
专业解释:
工厂模式定义了一个创建对象的接口,但让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
通俗理解:
比如你走进一家甜品店,向服务员说想要一杯饮料,具体是什么饮料由服务员决定(可能是咖啡或果汁)。服务员就是这里的“工厂”,负责根据你的需求生产出具体的饮料产品。
示例代码(JavaScript):
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');
4. 建造者模式(Builder Pattern)
专业解释:
建造者模式将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。客户端不需要知道内部组件的具体构建细节。
通俗理解:
就如同组装一台电脑,你可以选择不同配置的CPU、内存、硬盘等配件,最后由装机员按照你的配置清单来组装。组装过程(Builder)是固定的,但最终产出的电脑配置各异。
示例代码(JavaScript):
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);
5. 原型模式(Prototype Pattern)
专业解释:
原型模式是一种复制已有对象作为新对象的方式,通过克隆原型对象并对其稍作修改来创建新的对象,而不是重新初始化一个新对象。
通俗理解:
就如同制作陶艺,工匠可以根据一个基础模型(原型)快速复制出相似的作品,然后再针对每个复制品进行个性化装饰。
示例代码(JavaScript,利用内置的Object.create()方法实现简单原型克隆):
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"
结构型模式
1. 适配器模式 (Adapter Pattern)
专业解释:
适配器模式将一个类的接口转换为客户希望的另一个接口,使原本不兼容的接口能协同工作。主要应用于当系统需要使用现有的类,但是接口不符合需求时。
通俗理解:
就像电源插头转换器,将不同标准的插头转为适应目的地插座的标准。
示例代码(JavaScript):
// 假设我们有一个现有接口
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(); // 输出 "执行特殊请求"
2. 桥接模式 (Bridge Pattern)
专业解释:
桥接模式将抽象部分与其实现部分分离,使它们可以独立变化。它主要用于解耦抽象和实现,从而让它们可以独立演化。
通俗理解:
比如电脑品牌和操作系统是两个维度的变化,桥接模式就是让电脑品牌可以选择不同的操作系统,二者互不影响。
示例代码(JavaScript):
// 抽象部分
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."
3. 装饰模式 (Decorator Pattern)
专业解释:
装饰模式动态地给一个对象添加一些额外的职责,提供比继承更有弹性的替代方案来扩展对象的功能。
通俗理解:
比如咖啡基础款可以加糖、加奶、加香草等,每一种装饰都是在原有基础上增加新特性,而不是每次都创建新的咖啡品种。
示例代码(JavaScript):
// 基础组件
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(
4. 组合模式 (Composite Pattern)
专业解释:
组合模式允许你将对象组合成树形结构来表现“整体-部分”层次结构,并且用户对单个对象和组合对象的使用具有一致性。
通俗理解:
就像文件夹和文件的关系,文件夹里可以包含文件和子文件夹,无论操作单个文件还是整个文件夹,都采用相同的方式。
示例代码(JavaScript):
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()); // 输出:"复合组件操作: 叶子节点操作"
5. 外观模式 (Facade Pattern)
专业解释:
外观模式为子系统中的一组接口提供一个统一的高层接口,简化了该子系统的使用。
通俗理解:
就如同电视机遥控器,它隐藏了电视机内部复杂的电路控制逻辑,只提供几个简单的按钮供用户操作。
示例代码(JavaScript):
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():
6. 享元模式 (Flyweight Pattern)
专业解释:
享元模式运用共享技术有效支持大量细粒度的对象,通过共享已存在的同类对象来大幅度减少创建新对象的数量,从而节省系统资源。
通俗理解:
例如一个大型游戏中大量的小兵角色,他们虽然个体差异不大,但数量众多,可以通过共享一部分数据来减少内存占用。
示例代码(JavaScript):
class Flyweight {private intrinsicState: string;constructor(intrinsicState: string) {this.intrinsicState = intrinsicState;}operation(extrinsicState: string): string {return `${this.intrinsicState}, ${extrinsicState}`;}
}class FlyweightFactory {private flyweights: Map<string, Flyweight> = 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"// 注意这里返回的是同一个享元对象,节省了内存
7. 代理模式 (Proxy Pattern)
专业解释:
代理模式为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不能或者不应该直接引用另一个对象,代理对象作为中间人起到中介作用。
通俗理解:
类似于明星经纪人,粉丝们通常不会直接接触明星,而是通过经纪人进行沟通和安排事务。
示例代码(JavaScript):
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项目中,通常我们会通过约定的方式来实现接口约束。
行为型模式
1. 模板方法模式 (Template Method Pattern)
专业解释:
在抽象类中定义一个基本算法的框架,而将一些步骤延迟到子类中实现。它允许子类在不修改整体算法结构的情况下重新定义某些步骤。
通俗理解:
就像烹饪菜谱,给出了做一道菜的基本流程,但具体每个步骤的实现(如炒菜调料的选择)由各个具体的菜品子类决定。
示例代码(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();
2. 命令模式 (Command Pattern)
专业解释:
将一个请求封装为一个对象,使得可以用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。
通俗理解:
如同遥控器上的按键,每一个按键代表一个命令,按下按键就能执行相应的操作,还能实现撤销操作等功能。
示例代码(JavaScript):
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(); // 输出 "撤销命令"
3. 迭代器模式 (Iterator Pattern)
专业解释:
提供一种方法顺序访问聚合对象的各个元素,而又不暴露其底层表示。迭代器模式定义了一个访问一系列元素的接口,各元素之间关系紧密但又不需要暴露细节。
通俗理解:
就像看书目录,可以按照一定的顺序一页页翻看,而无需了解书的具体装订方式。
示例代码(JavaScript):
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"
}
4. 观察者模式 (Observer Pattern)
专业解释:
定义了对象之间的依赖关系,一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。
通俗理解:
像订阅新闻一样,当你订阅了某个主题后,每当有新的新闻更新时,你就会收到通知。
示例代码(JavaScript):
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"
5. 访问者模式 (Visitor Pattern)
专业解释:
封装一些作用于某种数据结构中的各种元素的操作,它可以在不改变元素类的前提下定义作用于这些元素的新操作。
通俗理解:
类似检查员去多个部门审核,各部门只需提供接受检查的接口,而无需关心检查的具体规则,检查员则携带不同规则去访问各部门。
示例代码(JavaScript):
// 定义元素接口
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"
6. 中介者模式 (Mediator Pattern)
专业解释:
定义一个中介对象来封装一系列的对象交互,使各对象不需要显式地相互引用,从而降低耦合度,同时使得系统易于扩展。
通俗理解:
就像公司内部员工有问题不直接相互联系,而是通过人事部门作为中介进行协调,这样避免了员工间的直接依赖关系。
示例代码(JavaScript):
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"
7. 备忘录模式 (Memento Pattern)
专业解释:
在不破坏封装性的前提下,捕获一个对象的内部状态以便稍后恢复。这种模式主要用于数据备份和还原操作,防止外部对象随意修改内部状态。
通俗理解:
就像游戏存档,你可以随时保存游戏进度并在任何时候恢复到之前的状态。
示例代码(JavaScript):
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));
8. 解释器模式 (Interpreter Pattern)
专业解释:
给定一门语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
通俗理解:
就像是编程语言的编译器或解释器,它解析程序员写的代码并执行相应操作。
示例代码(JavaScript)(由于解释器模式较为复杂且一般用于特定领域语言解析,以下仅为简单示意,真实场景会更加复杂):
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);
9. 状态模式 (State Pattern)
专业解释:
允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
通俗理解:
就像交通信号灯,红绿黄三种状态决定了不同的行为表现。
示例代码(JavaScript):
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."
10. 策略模式 (Strategy Pattern)
专业解释:
定义一系列算法,把它们一个个封装起来,并且使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户。
通俗理解:
如同不同的折扣计算策略,可以灵活切换,不影响使用折扣策略的购物车系统。
示例代码(JavaScript):
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)); // 输出 600discountedShoppingCart.setStrategy(new DiscountStrategy(0.2));
console.log(discountedShoppingCart.calculateTotalPrice(items)); // 输出 480
11. 职责链模式 (Chain of Responsibility Pattern)
专业解释:
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。
通俗理解:
就像公司的请假审批流程,员工提交请假申请后,申请会按照经理、总监、总经理的顺序逐级审批,直到某一级别负责人批准或拒绝为止。
示例代码(JavaScript):
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."
相关文章:
JS的设计模式(23种)
JavaScript设计模式是指在JavaScript编程中普遍应用的一系列经过验证的最佳实践和可重用的解决方案模板,它们用来解决在软件设计中频繁出现的问题,如对象的创建、职责分配、对象间通信以及系统架构等。 设计模式并不特指某个具体的代码片段,…...

[自研开源] MyData v0.7.5 更新日志
开源地址:gitee | github 详细介绍:MyData 基于 Web API 的数据集成平台 部署文档:用 Docker 部署 MyData 使用手册:MyData 使用手册 试用体验:https://demo.mydata.work 交流Q群:430089673 介绍 MyData …...

3月份的倒数第二个周末有感
坐在图书馆的那一刻,忽然感觉时间的节奏开始放缓。今天周末因为我们两都有任务需要完成,所以就选了嘉定图书馆,不得不说嘉定新城远香湖附近的图书馆真的很有感觉。然我不经意回想起学校的时光,那是多么美好且短暂的时光。凝视着窗…...
Java 变得越来越像 Rust
Java 变得越来越像 Rust 介绍 随着编程的增强和复杂性越来越流行,许多编程语言也相互效仿。 Java 也不例外。 尽管社区内部存在问题,Rust 仍逐年赢得了开发人员的喜爱。并且有充分的理由:由于编译器,Rust 使开发人员能够避免整…...
通过git bash 或命令行ssh访问服务器 sftp上传下载文件
上传下载文件 sftp -P 端口 appywIP 示例:sftp -P 10022 appyw25.222.133.222 然后输入密码即可 ls 查看文件 lls 查看本地文件 cd 跳转 lcd 本地跳转 get ... 下载文件 put 本地文件名 远程文件夹 //上传文件 put -r 本地文件夹 远程文件夹 //上传文件夹服务器…...

27 OpenCV 凸包
文章目录 概念Graham扫描算法convexHull 凸包函数示例 概念 什么是凸包(Convex Hull),在一个多变形边缘或者内部任意两个点的连线都包含在多边形边界或者内部。 正式定义: 包含点集合S中所有点的最小凸多边形称为凸包 Graham扫描算法 首先选择Y方向最低…...

【GPT概念04】仅解码器(only decode)模型的解码策略
一、说明 在我之前的博客中,我们研究了关于生成式预训练转换器的整个概述,以及一篇关于生成式预训练转换器(GPT)的博客——预训练、微调和不同的用例应用。现在让我们看看所有仅解码器模型的解码策略是什么。 二、解码策略 在之前…...
蔚来-安全开发一面/二面
基本不怎么会渗透测试,本科期间有过大数据隐私保护(密码)的项目,硕士期间有个华为合作的项目一篇在投的ai安全论文 一面(45min) 1.介绍自己 2.介绍一下实习 3.场景题轰炸,主要针对实习中的场景,主要考察…...
Redis Cluster集群模式容器化部署
Redis Cluster集群模式容器化部署 安装Docker和docker-compose准备docker-compose文件准备Redis配置文件Linux内核参数优化启停Redis实例Redis集群搭建 环境准备: IP版本角色端口172.x.x.11RHEL 7.9master6379172.x.x.12RHEL 7.9master6379172.x.x.13RHEL 7.9maste…...

网络原理(6)——IP协议
目录 一、网段划分 现在的网络划分: 1、一般情况下的家庭网络环境 2、IP地址 3、子网掩码 4、网关 以前的网络划分: 二、特殊IP 1、环回 IP 2、主机号为全 0 的IP 3、广播地址IP 三、路由选择(路线规划) 一、网段划分…...

淘宝商品详情API接口:快速获取商品信息的高效工具
淘宝商品详情API接口:快速获取商品信息的高效工具 请求示例,API接口接入Anzexi58 在信息化、数字化的今天,数据已成为商业决策的重要依据。对于电商行业而言,快速准确地获取商品信息对于商家和消费者都至关重要。淘宝作为中国最大…...

一分钟学习Markdown语法
title: 一分钟学习Markdown语法 date: 2024/3/24 19:33:29 updated: 2024/3/24 19:33:29 tags: MD语法文本样式列表结构链接插入图片展示练习实践链接问题 欢迎来到Markdown语法的世界!Markdown是一种简单而直观的标记语言,让文本排版变得轻松有趣。接下…...
Power Apps 学习笔记 -- OrganizationRequestCollection
文章目录 1. OrganizationRequestCollection 简介2. OrganizationRequestCollection2.1 OrganizationRequest 使用2.2 OrganizationRequestCollection 使用 1. OrganizationRequestCollection 简介 OrganizationRequestCollection 链接 : OrganizationRequestCollection Orga…...

python绘图matplotlib——使用记录1
本博文来自于网络收集,如有侵权请联系删除 使用matplotlib绘图 1 常用函数汇总1.1 plot1.2 legend1.3 scatter1.4 xlim1.5 xlabel1.6 grid1.7 axhline1.7 axvspan1.8 annotate1.9 text1.10 title 2 常见图形绘制2.1 bar——柱状图2.2 barh——条形图2.3 hist——直…...
Spring Data访问Elasticsearch----创建存储库实例
Spring Data访问Elasticsearch----创建存储库实例 一、Java配置二、XML配置三、使用过滤器四、独立使用 本文介绍如何为已定义的存储库接口创建实例和bean定义。 一、Java配置 在Java配置类上使用特定于存储的EnableElasticsearchRepositories注解来定义用于存储库激活的配置。…...

Wireshark TS | DNS 案例分析之外的思考
前言 承接之前一篇《Packet Challenge 之 DNS 案例分析》,在数据包跟踪文件 dnsing.pcapng 中,关于第 4 题(What is the largest DNS response time seen in this trace file? )的分析过程中曾经碰到一个小问题,主要…...
nginx集群部署访问不了怎么解决
如果你的Nginx集群部署无法访问,可能有多种原因导致,以下是一些常见的解决方法: 检查网络连接:确保服务器之间的网络连接是正常的,可以通过ping命令或telnet命令检查服务器之间的网络连通性。 检查防火墙设置ÿ…...
抖音小程序开发资质认证流程和资料
开发小程序前,你需要先入驻抖音开发平台开发者平台。包含注册账号、主体认证和对公认证,具体操作可参考注册开发者平台账号。 基础信息提交需要资料 1、营业执照。 本文介绍开发者在抖音开放平台的入驻流程。 入驻标准 入驻主体主要为中国或海外…...

【JAVA】通过JAVA实现用户界面的登录
🌈个人主页: Aileen_0v0 🔥热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法|MySQL| 💫个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-wyCvaz0EBNwHcwsi {font-family:"trebuchet ms",verdana,arial,sans-serif;f…...

UE5 C++ 3D血条 响应人物受伤 案例
一.3Dwidget 1.创建C Userwidget的 MyHealthWidget,声明当前血量和最大血量 UCLASS() class PRACTICEC_API UMyHealthWidget : public UUserWidget {GENERATED_BODY() public:UPROPERTY(EditAnywhere,BlueprintReadWrite,Category "MyWidget")float C…...

DAY 47
三、通道注意力 3.1 通道注意力的定义 # 新增:通道注意力模块(SE模块) class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...
unix/linux,sudo,其发展历程详细时间线、由来、历史背景
sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...

dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...

Springboot社区养老保险系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,社区养老保险系统小程序被用户普遍使用,为方…...

AI病理诊断七剑下天山,医疗未来触手可及
一、病理诊断困局:刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断",医生需通过显微镜观察组织切片,在细胞迷宫中捕捉癌变信号。某省病理质控报告显示,基层医院误诊率达12%-15%,专家会诊…...

NXP S32K146 T-Box 携手 SD NAND(贴片式TF卡):驱动汽车智能革新的黄金组合
在汽车智能化的汹涌浪潮中,车辆不再仅仅是传统的交通工具,而是逐步演变为高度智能的移动终端。这一转变的核心支撑,来自于车内关键技术的深度融合与协同创新。车载远程信息处理盒(T-Box)方案:NXP S32K146 与…...

R语言速释制剂QBD解决方案之三
本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》第一个处方的R语言解决方案。 第一个处方研究评估原料药粒径分布、MCC/Lactose比例、崩解剂用量对制剂CQAs的影响。 第二处方研究用于理解颗粒外加硬脂酸镁和滑石粉对片剂质量和可生产…...

Windows安装Miniconda
一、下载 https://www.anaconda.com/download/success 二、安装 三、配置镜像源 Anaconda/Miniconda pip 配置清华镜像源_anaconda配置清华源-CSDN博客 四、常用操作命令 Anaconda/Miniconda 基本操作命令_miniconda创建环境命令-CSDN博客...
【前端异常】JavaScript错误处理:分析 Uncaught (in promise) error
在前端开发中,JavaScript 异常是不可避免的。随着现代前端应用越来越多地使用异步操作(如 Promise、async/await 等),开发者常常会遇到 Uncaught (in promise) error 错误。这个错误是由于未正确处理 Promise 的拒绝(r…...