JavaScript设计模式是一种在JavaScript开发中常用的编程模式,用于解决特定问题的代码组织和架构。通过使用设计模式,可以提高代码的可读性、可维护性和可扩展性。
以下是几种常见的JavaScript设计模式:
单例模式(Singleton Pattern):确保一个类只有一个实例,并提供全局访问点。
工厂模式(Factory Pattern):根据参数的不同,创建不同的对象实例,隐藏具体的实现细节。
观察者模式(Observer Pattern):定义了一种一对多的依赖关系,改变一个对象的状态会自动通知其他依赖该对象的对象。
原型模式(Prototype Pattern):通过复制现有对象来创建新的对象,避免了使用类和子类的层次结构。
装饰者模式(Decorator Pattern):动态地给对象添加新的功能,通过包装原对象来实现。
迭代器模式(Iterator Pattern):提供一种方法顺序访问一个容器对象中的各个元素,而不用暴露其内部实现。
适配器模式(Adapter Pattern):将一个类的接口转换成客户端所期望的另一个接口,使得原本由于接口不兼容而不能一起工作的类可以一起工作。
策略模式(Strategy Pattern):定义一系列算法,将它们封装起来,并使它们可以相互替换,使得算法可以独立于客户端而变化。
模板方法模式(Template Method Pattern):定义一个操作中的算法的骨架,将一些步骤延迟到子类中实现。
这些设计模式可以根据实际需求进行选择和使用,它们帮助开发者更好地组织和管理代码,提高代码的可维护性和复用性。
1-构造器模式
在JavaScript中,构造器模式是通过定义一个函数来初始化新创建的对象实例。这个函数通常以大写字母开头,表示它是一个构造函数(尽管这不是强制的,但这是一个广泛遵循的约定)。下面是一个简单的Node.js中构造器模式的例子:
// 定义一个构造函数,用于创建电影对象
function Movie(title, director, releaseYear) {
// 将属性赋值给新创建的对象实例
this.title = title;
this.director = director;
this.releaseYear = releaseYear;
// 可以添加一些方法到原型上,这样所有实例可以共享这些方法
this.getInfo = function() {
return `${this.title} directed by ${this.director}, released in ${this.releaseYear}.`;
};
}
// 使用构造函数创建电影实例
let movie1 = new Movie('The Matrix', 'Lana Wachowski, Lilly Wachowski', 1999);
let movie2 = new Movie('Inception', 'Christopher Nolan', 2010);
// 访问和使用实例的属性和方法
console.log(movie1.getInfo()); // 输出: "The Matrix directed by Lana Wachowski, Lilly Wachowski, released in 1999."
console.log(movie2.getInfo()); // 输出: "Inception directed by Christopher Nolan, released in 2010."
// 检查实例类型
console.log(movie1 instanceof Movie); // 输出: true
在这个例子中,Movie
就是一个构造函数,当用new
关键字调用时,它会创建并返回一个新的对象,并且这个对象的__proto__
指向Movie.prototype
。这样,每个由Movie
构造函数创建的实例都将拥有其赋予的属性(如title
、director
和releaseYear
)以及从原型继承的方法(如getInfo
)。
2-原型模式
在JavaScript中,原型模式是通过对象的prototype
属性来实现对象间的属性和方法共享。在Node.js环境中,原型模式的使用与浏览器环境中的JavaScript是一致的,因为Node.js同样基于V8引擎运行JavaScript代码。下面是一个简单的Node.js中原型模式的例子:
// 定义一个原型对象
function Animal(name) {
this.name = name;
}
// 在Animal的原型上定义一个方法
Animal.prototype.speak = function() {
console.log(`${this.name} makes a sound.`);
}
// 创建Animal的实例
let dog = new Animal('Rex');
let cat = new Animal('Whiskers');
// 实例可以访问到原型上的方法
dog.speak(); // 输出: "Rex makes a sound."
cat.speak(); // 输出: "Whiskers makes a sound."
// 检查实例是否继承了Animal的原型
console.log(dog instanceof Animal); // 输出: true
在这个例子中,Animal
函数充当构造器,并且其prototype
属性指向了一个对象,该对象包含所有将被Animal
实例共享的方法(如speak
)。当我们创建dog
和cat
两个新实例时,它们会继承并能够调用Animal.prototype
上的方法。这就是JavaScript中的原型链机制,它是实现面向对象编程中继承和共享行为的基础。
3-工厂模式
在Node.js中,工厂模式是一种设计模式,用于封装对象的创建过程,使得客户端(调用者)不需要知道具体的实现细节,只需要传递必要的参数来获取所需的对象实例。下面是一个简单的工厂函数的例子:
// 定义一个工厂函数
function createVehicle(type) {
// 根据传入的类型创建并返回不同的对象实例
switch (type) {
case 'car':
return new Car();
case 'truck':
return new Truck();
case 'motorcycle':
return new Motorcycle();
default:
throw new Error('Invalid vehicle type');
}
}
// 定义各种车辆类
function Car() {
this.drive = function() {
console.log('Driving a car...');
};
}
function Truck() {
this.drive = function() {
console.log('Driving a truck...');
};
}
function Motorcycle() {
this.drive = function() {
console.log('Driving a motorcycle...');
};
}
// 使用工厂函数创建对象
let myCar = createVehicle('car');
let myTruck = createVehicle('truck');
let myMotorcycle = createVehicle('motorcycle');
// 调用对象的方法
myCar.drive(); // 输出: "Driving a car..."
myTruck.drive(); // 输出: "Driving a truck..."
myMotorcycle.drive(); // 输出: "Driving a motorcycle..."
// 尝试创建一个无效类型的车辆
try {
let invalidVehicle = createVehicle('boat');
} catch (error) {
console.error(error.message); // 输出: "Invalid vehicle type"
}
在这个例子中,createVehicle
函数作为一个工厂,根据传入的类型字符串动态地创建不同类型的车辆对象。这样,调用者无需关心如何实例化具体车辆类,只需关注它们共有的接口(比如drive
方法)。
4-抽象工厂模式
在Node.js中,抽象工厂模式是一种创建型设计模式,它提供了一个接口用于创建相关或依赖对象的家族,而不需要指定具体的类。下面是一个简单的抽象工厂模式在Node.js中的实现例子:
// 定义抽象工厂接口
class AbstractVehicleFactory {
constructor() {
if (this.constructor === AbstractVehicleFactory) {
throw new TypeError("Abstract class cannot be instantiated directly.");
}
}
// 抽象方法,子类必须实现
createCar() {
throw new Error('createCar must be implemented in subclass');
}
createTruck() {
throw new Error('createTruck must be implemented in subclass');
}
}
// 创建具体工厂 - 品牌A工厂
class BrandAFactory extends AbstractVehicleFactory {
createCar() {
return new CarBrandA();
}
createTruck() {
return new TruckBrandA();
}
}
// 创建具体工厂 - 品牌B工厂
class BrandBFactory extends AbstractVehicleFactory {
createCar() {
return new CarBrandB();
}
createTruck() {
return new TruckBrandB();
}
}
// 定义车辆的具体类
class CarBrandA {
drive() {
console.log('Driving a Car from Brand A...');
}
}
class TruckBrandA {
drive() {
console.log('Driving a Truck from Brand A...');
}
}
class CarBrandB {
drive() {
console.log('Driving a Car from Brand B...');
}
}
class TruckBrandB {
drive() {
console.log('Driving a Truck from Brand B...');
}
}
// 使用抽象工厂创建不同品牌的车辆
let brandAFactory = new BrandAFactory();
let carA = brandAFactory.createCar();
let truckA = brandAFactory.createTruck();
carA.drive(); // 输出: "Driving a Car from Brand A..."
truckA.drive(); // 输出: "Driving a Truck from Brand A..."
let brandBFactory = new BrandBFactory();
let carB = brandBFactory.createCar();
let truckB = brandBFactory.createTruck();
carB.drive(); // 输出: "Driving a Car from Brand B..."
truckB.drive(); // 输出: "Driving a Truck from Brand B..."
// 不直接实例化抽象工厂
try {
let abstractFactory = new AbstractVehicleFactory();
} catch (error) {
console.error(error.message); // 抛出错误,因为不能直接实例化抽象类
}
在这个例子中,AbstractVehicleFactory
是抽象工厂,定义了创建汽车和卡车的方法(但未实现)。然后我们有两个具体工厂BrandAFactory
和BrandBFactory
实现了这些方法,分别返回品牌A和品牌B的汽车和卡车对象。这样客户端代码只需与抽象工厂交互,就可以根据需求灵活地创建不同品牌的汽车和卡车实例。
5-建造者模式
在Node.js中,建造者模式是一种创建型设计模式,它允许通过分步骤构建复杂对象,并隐藏具体的构造过程。下面是一个简单的Node.js中的建造者模式例子:
// 定义抽象建造者接口
class VehicleBuilder {
constructor() {
if (this.constructor === VehicleBuilder) {
throw new Error("Abstract class cannot be instantiated directly.");
}
}
// 抽象方法,子类必须实现
buildFrame() {
throw new Error('buildFrame must be implemented in subclass');
}
buildEngine() {
throw new Error('buildEngine must be implemented in subclass');
}
buildWheels() {
throw new Error('buildWheels must be implemented in subclass');
}
buildDoors() {
throw new Error('buildDoors must be implemented in subclass');
}
getVehicle() {
throw new Error('getVehicle must be implemented in subclass');
}
}
// 创建具体建造者 - 轿车建造者
class CarBuilder extends VehicleBuilder {
constructor() {
super();
this.vehicle = { type: 'Car' };
}
buildFrame() {
this.vehicle.frame = 'Car Frame';
}
buildEngine() {
this.vehicle.engine = 'Turbo Engine';
}
buildWheels() {
this.vehicle.wheels = '4 Wheels';
}
buildDoors() {
this.vehicle.doors = '4 Doors';
}
getVehicle() {
return this.vehicle;
}
}
// 创建具体建造者 - 卡车建造者
class TruckBuilder extends VehicleBuilder {
constructor() {
super();
this.vehicle = { type: 'Truck' };
}
buildFrame() {
this.vehicle.frame = 'Heavy Duty Frame';
}
buildEngine() {
this.vehicle.engine = 'Diesel Engine';
}
buildWheels() {
this.vehicle.wheels = '6 Wheels';
}
buildDoors() {
this.vehicle.doors = '2 Doors';
}
getVehicle() {
return this.vehicle;
}
}
// 定义导演者(Director)角色
class VehicleDirector {
constructor(builder) {
this.builder = builder;
}
constructVehicle() {
this.builder.buildFrame();
this.builder.buildEngine();
this.builder.buildWheels();
this.builder.buildDoors();
return this.builder.getVehicle();
}
}
// 使用建造者和导演者来创建车辆
let carBuilder = new CarBuilder();
let director = new VehicleDirector(carBuilder);
let car = director.constructVehicle();
console.log(car); // 输出:{ type: 'Car', frame: 'Car Frame', engine: 'Turbo Engine', wheels: '4 Wheels', doors: '4 Doors' }
let truckBuilder = new TruckBuilder();
director.builder = truckBuilder;
let truck = director.constructVehicle();
console.log(truck); // 输出:{ type: 'Truck', frame: 'Heavy Duty Frame', engine: 'Diesel Engine', wheels: '6 Wheels', doors: '2 Doors' }
在这个例子中,VehicleBuilder
是抽象建造者,定义了一系列用于构建车辆的接口。而CarBuilder
和TruckBuilder
是具体建造者,它们实现了这些接口以分别构建轿车和卡车。VehicleDirector
扮演了导演的角色,它知道如何按照正确的顺序调用建造者的构建方法来完成整个车辆的构建过程。这样,客户端代码只需要与导演交互,就可以根据需要动态地生成不同类型的车辆对象。
6-单例模式
在Node.js中,由于模块加载机制的特性(每个模块只会被加载一次),我们可以通过模块系统天然地实现单例模式。下面是一个简单的Node.js单例模式实现的例子:
// 单例模块:singleton.js
let instance;
class Singleton {
constructor() {
if (!instance) {
// 初始化逻辑
this.data = 'Some important data';
instance = this;
}
return instance;
}
getData() {
return this.data;
}
setData(newData) {
this.data = newData;
}
}
module.exports = new Singleton(); // 直接导出单例实例
// 使用该单例的模块:app.js
const singleton = require('./singleton');
console.log(singleton.getData()); // 输出: 'Some important data'
singleton.setData('New important data');
console.log(singleton.getData()); // 输出: 'New important data'
// 在另一个文件或模块中同样引入这个单例
const sameSingleton = require('./singleton');
console.log(sameSingleton.getData()); // 输出: 'New important data'
在这个例子中,singleton.js
模块通过检查是否存在instance
变量来确保只有一个实例存在。当第一次调用构造函数时,它会初始化数据并设置instance
指向当前实例。后续任何对模块的导入都会直接返回已经创建好的单例实例,从而保证了全局范围内仅有一个该类的实例。
7-装饰器模式
在Node.js中,装饰器模式是一种设计模式,它允许我们在不修改对象(类)自身的基础上动态地给对象添加额外的职责或行为。由于JavaScript原生并不直接支持装饰器语法,但ES6引入了装饰器提案,并且TypeScript语言已经支持该特性,因此我们可以使用TypeScript来演示一个装饰器模式的例子,然后通过编译成JavaScript在Node.js环境下运行。
以下是一个使用TypeScript实现的装饰器模式示例:
// 装饰器声明文件:decorator.ts
// 定义一个装饰器工厂函数
function loggable(target) {
// 原型方法注入日志功能
const originalMethod = target.prototype.print;
target.prototype.print = function(...args: any[]) {
console.log(`Calling print method with args: ${JSON.stringify(args)}`);
return originalMethod.apply(this, args);
};
}
// 需要被装饰的目标类
class MyService {
print(message: string) {
console.log(message);
}
}
// 应用装饰器
@loggable
class EnhancedMyService extends MyService {}
// 使用增强后的服务
const enhancedService = new EnhancedMyService();
enhancedService.print('Hello, World!');
// 编译为JavaScript后,在Node.js环境中运行
// JavaScript版本:
(function() {
"use strict";
function loggable(target) {
var _a;
const originalMethod = (_a = target.prototype.print) !== null && _a !== void 0 ? _a : function () { };
target.prototype.print = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
console.log(`Calling print method with args: ${JSON.stringify(args)}`);
return originalMethod.apply(this, args);
};
}
class MyService {
print(message) {
console.log(message);
}
}
let EnhancedMyService = class EnhancedMyService extends MyService {
};
EnhancedMyService = __decorate([
loggable
], EnhancedMyService);
const enhancedService = new EnhancedMyService();
enhancedService.print('Hello, World!');
})();
在这个例子中,loggable
装饰器用于增强MyService
类的print
方法,在调用原始打印方法前添加日志输出的功能。当EnhancedMyService
类使用@loggable
装饰器时,其原型上的print
方法会被替换为包含日志功能的新方法。在Node.js环境中运行编译后的JavaScript代码,即可观察到装饰器的效果。
8-适配器模式
在Node.js中,适配器模式用于将一个类的接口转换为另一种客户期望的接口。以下是一个简单的适配器模式的例子:
// 假设我们有一个遗留的API库(或第三方模块)提供了一个非标准的对象
class LegacyAPI {
constructor() {
this.internalData = 'Legacy API data';
}
legacyMethod() {
return this.internalData;
}
}
// 我们的代码期望使用具有特定方法名和签名的新接口
interface ModernAPI {
fetchData(): string;
}
// 创建适配器类,实现新接口并委托给遗留API对象
class LegacyToModernAdapter extends LegacyAPI implements ModernAPI {
// 实现新接口的方法
fetchData() {
// 使用遗留API的方法来获取数据,并进行必要的转换以满足新接口的要求
return this.legacyMethod();
}
}
// 客户端代码只与新接口交互
function processData(api: ModernAPI) {
const data = api.fetchData();
console.log('Received data:', data);
}
// 创建遗留API实例并通过适配器将其包装为现代API
const legacyApi = new LegacyAPI();
const adaptedApi = new LegacyToModernAdapter();
// 将适配后的API传入处理函数
processData(adaptedApi); // 输出:Received data: Legacy API data
在这个例子中,LegacyAPI
是现有不兼容接口的实现,而ModernAPI
是我们期望使用的接口规范。通过创建一个适配器类LegacyToModernAdapter
,我们能够将LegacyAPI
的方法转换成符合ModernAPI
规范的形式,从而使得遗留API能够在新的、统一的接口下工作。
9-策略模式
在Node.js中,策略模式是一种行为设计模式,它定义了算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。下面是一个简单的Node.js策略模式示例:
// 定义策略接口(或抽象类)
class ShippingStrategy {
calculate(shoppingCart) {
throw new Error('calculate method must be implemented');
}
}
// 创建具体的策略实现:普通快递策略
class NormalShippingStrategy extends ShippingStrategy {
calculate(shoppingCart) {
return shoppingCart.totalPrice * 0.1; // 假设普通快递费用是总价的10%
}
}
// 创建具体的策略实现:加急快递策略
class ExpressShippingStrategy extends ShippingStrategy {
calculate(shoppingCart) {
return shoppingCart.totalPrice * 0.2; // 假设加急快递费用是总价的20%
}
}
// 客户端代码,包含上下文对象
class ShoppingCart {
constructor(strategy) {
this.strategy = strategy;
this.items = [];
}
addItem(item, price) {
this.items.push({ item, price });
this.calculateTotal();
}
removeItem(item) {
// ... 实现移除商品逻辑 ...
}
chooseShippingStrategy(strategy) {
this.strategy = strategy;
}
calculateTotal() {
this.totalPrice = this.items.reduce((sum, { price }) => sum + price, 0);
}
getShippingCost() {
return this.strategy.calculate(this);
}
}
// 使用示例
const normalShipping = new NormalShippingStrategy();
const expressShipping = new ExpressShippingStrategy();
const cart = new ShoppingCart(normalShipping);
cart.addItem('Product A', 50);
cart.addItem('Product B', 75);
console.log('Normal shipping cost:', cart.getShippingCost()); // 输出:12.5
cart.chooseShippingStrategy(expressShipping);
console.log('Express shipping cost:', cart.getShippingCost()); // 输出:25
// 在运行时可以根据需要切换不同的配送策略
在这个例子中,ShippingStrategy
是策略接口,而NormalShippingStrategy
和ExpressShippingStrategy
是两种不同的具体策略。ShoppingCart
作为上下文角色,持有并调用相应的策略来计算运费。通过改变购物车实例中的策略对象,我们可以动态地改变计算运费的行为。
10-代理模式
在Node.js中,代理模式是一种结构型设计模式,它为其他对象提供一个代理以控制对这个对象的访问。代理模式可以用于多种场景,如远程方法调用、缓存、权限控制等。下面是一个简单的Node.js代理模式示例:
// 首先定义原始(真实)主题接口或类
class RealSubject {
constructor() {
this.data = 'Some sensitive data';
}
getData() {
return this.data;
}
setData(newData) {
this.data = newData;
}
}
// 创建代理类实现相同的接口,并持有原始主题的引用
class ProxySubject {
constructor(realSubject) {
this.realSubject = realSubject;
}
// 在获取数据时添加额外的行为,例如权限检查
getData() {
if (this.isAuthorized()) {
return this.realSubject.getData();
} else {
throw new Error('Access denied');
}
}
// 在设置数据时添加额外的行为,例如日志记录
setData(newData) {
console.log(`Setting data to ${newData}`);
this.realSubject.setData(newData);
}
// 假设的授权检查方法
isAuthorized() {
// 真实环境中可能根据用户角色、令牌等进行更复杂的逻辑判断
return true; // 示例中简单地返回true,实际应用中会做具体验证
}
}
// 使用代理
const realSubject = new RealSubject();
const proxySubject = new ProxySubject(realSubject);
// 只有通过代理才能安全地操作原始对象
proxySubject.setData('New sensitive data');
console.log(proxySubject.getData()); // 输出: "New sensitive data"
// 如果没有通过代理直接访问原始对象,将会绕过附加的行为
// 这里为了演示不直接访问,但通常情况下可能由于封装或者私有属性限制无法直接访问
在这个例子中,RealSubject
是真实对象,拥有实际的数据和行为。ProxySubject
作为代理对象,对外提供了与真实对象相同的方法,但在执行这些方法之前或之后,可以增加一些额外的功能,比如上述示例中的权限检查和日志记录。客户端代码通过与ProxySubject
交互,间接地控制了对RealSubject
的访问。
11-观察者模式
在Node.js中,观察者模式(Observer Pattern)是一种行为设计模式,它定义了对象之间的一对多依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。以下是一个简单的Node.js实现观察者模式的例子:
// 定义 Subject(被观察者)
class Subject {
constructor() {
this.observers = [];
this.state = null;
}
// 添加观察者
addObserver(observer) {
this.observers.push(observer);
}
// 删除观察者
removeObserver(observer) {
const index = this.observers.indexOf(observer);
if (index > -1) {
this.observers.splice(index, 1);
}
}
// 通知所有观察者状态已改变
notifyObservers() {
this.observers.forEach((observer) => observer.update(this.state));
}
// 设置状态并触发通知
setState(newState) {
this.state = newState;
this.notifyObservers();
}
}
// 定义 Observer(观察者)
class Observer {
update(state) {
console.log(`Received new state: ${state}`);
}
}
// 使用示例
const subject = new Subject();
const observer1 = new Observer();
const observer2 = new Observer();
subject.addObserver(observer1);
subject.addObserver(observer2);
subject.setState('State A'); // 输出:Received new state: State A (两次)
subject.removeObserver(observer1);
subject.setState('State B'); // 输出:Received new state: State B (一次)
在这个例子中,Subject
类维护了一个观察者列表,并提供了添加、删除观察者以及更改状态后通知观察者的功能。Observer
类实现了接收新状态的update
方法。当Subject
的状态发生变化时,会调用notifyObservers
方法通知所有注册过的观察者。
通过这种方式,可以解耦主题与观察者之间的直接联系,使得系统更易于扩展和复用。
12-发布订阅模式
在Node.js中,发布订阅模式(Publish/Subscribe Pattern)是一种事件驱动的设计模式,通过消息中间件来解耦组件之间的通信。这种模式下,发布者不直接与订阅者交互,而是将消息发布到一个公共的“通道”或“主题”,而订阅者则可以监听和接收特定通道的消息。
下面是一个使用原生JavaScript实现的简单发布订阅模式的例子:
// 创建一个简单的发布订阅器类
class PubSub {
constructor() {
this.topics = {};
}
// 订阅一个话题
subscribe(topic, callback) {
if (!this.topics[topic]) {
this.topics[topic] = [];
}
this.topics[topic].push(callback);
}
// 取消订阅
unsubscribe(topic, callback) {
const callbacks = this.topics[topic];
if (callbacks) {
for (let i = 0; i < callbacks.length; i++) {
if (callbacks[i] === callback) {
callbacks.splice(i, 1);
break;
}
}
}
}
// 发布一个话题并传递参数给所有订阅者
publish(topic, ...args) {
const callbacks = this.topics[topic];
if (callbacks) {
callbacks.forEach((callback) => {
callback(...args);
});
}
}
}
// 使用示例
const pubsub = new PubSub();
// 订阅者A订阅'news'话题
pubsub.subscribe('news', (message) => {
console.log('Subscriber A received:', message);
});
// 订阅者B也订阅'news'话题
pubsub.subscribe('news', (message) => {
console.log('Subscriber B received:', message);
});
// 发布者发布一条新闻
pubsub.publish('news', 'Breaking news: Node.js v20.0 released!');
// 输出:
// Subscriber A received: Breaking news: Node.js v20.0 released!
// Subscriber B received: Breaking news: Node.js v20.0 released!
此外,在Node.js环境中,内置的events
模块已经实现了发布订阅模式,可以直接使用EventEmitter类来创建事件发布订阅系统:
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
myEmitter.on('news', (message) => {
console.log('Subscriber A received:', message);
});
myEmitter.on('news', (message) => {
console.log('Subscriber B received:', message);
});
myEmitter.emit('news', 'Breaking news: Node.js v20.0 released!');
这个例子同样会输出相同的结果。
13-模块模式
在Node.js中,模块是其核心特性之一,它使用CommonJS规范来实现模块化。每个文件(或模块)都有自己的作用域,且可以通过module.exports
和require()
函数来导出和导入模块。下面是一个简单的Node.js模块模式的例子:
myModule.js (定义一个模块)
// 导出一个对象作为模块接口
module.exports = {
add: function(a, b) {
return a + b;
},
subtract: function(a, b) {
return a - b;
},
multiply: function(a, b) {
return a * b;
},
divide: function(a, b) {
if (b !== 0) {
return a / b;
} else {
throw new Error('Cannot divide by zero');
}
}
};
app.js (使用这个模块)
// 导入上面定义的模块
const math = require('./myModule');
console.log(math.add(5, 3)); // 输出:8
console.log(math.subtract(10, 4)); // 输出:6
console.log(math.multiply(2, 7)); // 输出:14
try {
console.log(math.divide(10, 2));
} catch (error) {
console.error(error.message);
}
// 注意这里我们不是导入具体的函数,而是导入包含多个方法的对象
在这个例子中,myModule.js
是一个自定义模块,它通过module.exports
导出了一个包含了多个数学运算方法的对象。而在app.js
中,我们通过require()
函数导入了这个模块,并将其赋值给变量math
,这样就可以像操作本地对象一样调用模块中的方法。这就是Node.js中的模块模式应用。
14-桥接模式
在Node.js中,桥接模式是一种结构型设计模式,它允许对象在其内部结构(实现)与它的外部接口(抽象)之间进行解耦。这种模式使得抽象部分和实现部分可以独立变化。
以下是一个简单的Node.js桥接模式例子,用于表示不同的图形在不同的上下文中绘制:
// 抽象部分 - 图形接口
class DrawingAPI {
drawCircle(radius, x, y) {
throw new Error('drawCircle must be implemented in subclass');
}
}
// 实现部分 - 绘制API的两种不同实现
class DrawingAPI1 extends DrawingAPI {
drawCircle(radius, x, y) {
console.log(`Drawing a circle with radius ${radius} at (${x}, ${y}) using API v1`);
}
}
class DrawingAPI2 extends DrawingAPI {
drawCircle(radius, x, y) {
console.log(`Drawing a circle with radius ${radius} at (${x}, ${y}) using API v2 (with advanced features)`);
}
}
// 抽象部分 - 形状接口
class Shape {
constructor(drawingAPI) {
this.drawingAPI = drawingAPI;
}
draw() {
throw new Error('draw method must be implemented in subclass');
}
}
// 具体形状 - 圆形
class CircleShape extends Shape {
constructor(radius, drawingAPI) {
super(drawingAPI);
this.radius = radius;
}
draw() {
this.drawingAPI.drawCircle(this.radius, 0, 0); // 假设圆心在(0, 0)
}
}
// 客户端代码
const shape = new CircleShape(5, new DrawingAPI1());
shape.draw(); // 输出: "Drawing a circle with radius 5 at (0, 0) using API v1"
const anotherShape = new CircleShape(7, new DrawingAPI2());
anotherShape.draw(); // 输出: "Drawing a circle with radius 7 at (0, 0) using API v2 (with advanced features)"
在这个例子中,Shape
类是抽象部分,它定义了基本的行为(draw()
方法),并持有对具体绘图API的引用。CircleShape
是Shape
的一个具体实现,它依赖于具体的绘图API来完成实际的绘制工作。而DrawingAPI1
和DrawingAPI2
则是实现部分,它们提供了不同方式绘制圆形的具体逻辑。客户端可以根据需要选择不同的绘图API版本,并且不影响形状本身的定义。
15-组合模式
在Node.js中,组合模式是一种结构型设计模式,它允许你将对象组合成树形结构,并且可以以统一的方式处理单个对象和对象组合。这种模式模拟了部分-整体的层次结构关系,使得客户端代码可以一致地处理单个对象和组合对象。
以下是一个简单的Node.js组合模式的例子,模拟文件系统中的目录与文件:
class FileSystemObject {
constructor(name) {
this.name = name;
this.children = [];
}
add(child) {
this.children.push(child);
}
remove(child) {
const index = this.children.indexOf(child);
if (index !== -1) {
this.children.splice(index, 1);
}
}
printStructure(prefix = '') {
console.log(`${prefix}${this.name}`);
for (const child of this.children) {
child.printStructure(prefix + ' ');
}
}
}
class Directory extends FileSystemObject {
constructor(name) {
super(name);
}
}
class File extends FileSystemObject {
constructor(name) {
super(name);
}
// 对于文件,打印时可能需要额外的信息,比如大小等
printStructure(prefix = '') {
console.log(`${prefix}${this.name} (File)`);
}
}
// 创建并组合对象
const root = new Directory('root');
const folder1 = new Directory('Folder1');
const folder2 = new Directory('Folder2');
const file1 = new File('file1.txt');
const file2 = new File('file2.txt');
folder1.add(file1);
folder2.add(file2);
root.add(folder1);
root.add(folder2);
// 打印文件系统的结构
root.printStructure();
在这个例子中,FileSystemObject
是抽象组件,定义了所有文件系统对象共有的行为(添加、删除子项以及打印结构)。Directory
和File
都是FileSystemObject
的具体实现,它们都继承了这些行为。Directory
类可以包含其他Directory
或File
对象作为其子项,而File
则表示不能进一步包含子项的对象。通过这种方式,我们能够构建一个反映实际文件系统层级结构的组合对象。
16-命令模式
在Node.js中,命令模式是一种行为设计模式,它将请求封装为一个对象(即命令),从而使得可以用不同的请求、队列请求或者可以撤销请求。下面是一个简单的Node.js命令模式的例子:
// 定义Command接口或抽象类
class Command {
execute() {
throw new Error('execute method must be implemented');
}
}
// 具体的命令实现 - 打开文件命令
class OpenFileCommand extends Command {
constructor(fileSystem) {
super();
this.fileSystem = fileSystem;
}
execute(fileName) {
console.log(`Opening file: ${fileName}`);
// 在实际应用中,fileSystem.open(fileName) 会打开指定的文件
// 这里简化为打印信息
// this.fileSystem.open(fileName);
}
}
// 具体的命令实现 - 关闭文件命令
class CloseFileCommand extends Command {
constructor(fileSystem) {
super();
this.fileSystem = fileSystem;
}
execute(fileName) {
console.log(`Closing file: ${fileName}`);
// 在实际应用中,fileSystem.close(fileName) 会关闭指定的文件
// 这里简化为打印信息
// this.fileSystem.close(fileName);
}
}
// 请求者角色(Invoker)
class Invoker {
constructor() {
this.commandList = [];
}
setCommand(command) {
this.commandList.push(command);
}
invokeCommands() {
this.commandList.forEach((command) => command.execute());
}
}
// 客户端代码
const fileSystem = {}; // 假设这是一个包含open和close方法的对象
const openCommand = new OpenFileCommand(fileSystem);
const closeCommand = new CloseFileCommand(fileSystem);
const invoker = new Invoker();
invoker.setCommand(openCommand);
invoker.setCommand(closeCommand);
invoker.invokeCommands();
// 输出:
// Opening file: undefined
// Closing file: undefined
// 如果需要传入具体的文件名,在设置命令时传递参数
invoker.setCommand(new OpenFileCommand(fileSystem, 'example.txt'));
invoker.invokeCommands();
// 输出:
// Opening file: example.txt
在这个例子中,OpenFileCommand
和CloseFileCommand
是具体命令的实现,它们都继承自抽象的Command
并实现了execute
方法。Invoker
作为请求者角色,负责存储命令并在需要时执行它们。通过这种方式,客户端只需与Invoker
交互,而无需直接操作具体命令的执行细节。
17-模板方法模式
在Node.js中,模板方法模式是一种行为设计模式,它定义了一个操作中的算法骨架,并允许子类重写算法的某些步骤。以下是一个简单的Node.js模板方法模式的例子:
// 定义抽象类或基类,包含模板方法
class AbstractCook {
cookRecipe() {
this.prepareIngredients();
this.mixIngredients();
this.cook();
this.decorate();
console.log('Meal is ready!');
}
// 模板方法中的基本操作,部分被声明为抽象方法,需要子类实现
prepareIngredients() {
throw new Error('prepareIngredients must be implemented in subclass');
}
mixIngredients() {
throw new Error('mixIngredients must be implemented in subclass');
}
// 可选的基本操作,子类可以选择性地覆盖这些方法
cook() {
console.log('Cooking...');
}
decorate() {
console.log('Decorating the dish...');
}
}
// 具体的厨师类,继承自抽象厨师并实现抽象方法
class ItalianCook extends AbstractCook {
prepareIngredients() {
console.log('Preparing pasta and tomato sauce');
}
mixIngredients() {
console.log('Mixing pasta with sauce');
}
}
// 创建意大利厨师对象并执行烹饪过程
const italianCook = new ItalianCook();
italianCook.cookRecipe();
// 输出:
// Preparing pasta and tomato sauce
// Mixing pasta with sauce
// Cooking...
// Decorating the dish...
// Meal is ready!
在这个例子中,AbstractCook
类定义了制作一道菜的主要步骤(模板方法),其中一些步骤是抽象的,由具体子类(如ItalianCook
)提供实现。ItalianCook
实现了prepareIngredients
和mixIngredients
方法,而其他步骤如cook
和decorate
则使用父类提供的默认实现。当调用ItalianCook
实例的cookRecipe
方法时,会按照预设的顺序执行所有步骤,从而完成整个菜肴的制作流程。
18-迭代器模式
在Node.js中,迭代器模式允许遍历不同数据结构的元素而无需暴露底层表示。ES6引入了原生的迭代器接口,使得开发者可以更容易地实现和使用迭代器模式。
以下是一个简单的Node.js迭代器模式的例子,展示了如何创建一个自定义迭代器来遍历数组:
// 创建一个可迭代对象(类或构造函数)
class MyIterable {
constructor(data) {
this.data = data;
}
// 定义Symbol.iterator方法以返回一个迭代器
[Symbol.iterator]() {
let index = 0;
const data = this.data;
return {
next: () => {
if (index < data.length) {
return { value: data[index++], done: false };
} else {
return { done: true };
}
},
};
}
}
// 使用迭代器
const iterable = new MyIterable([1, 2, 3, 4, 5]);
for (let value of iterable) {
console.log(value);
}
// 输出:
// 1
// 2
// 3
// 4
// 5
在这个例子中,MyIterable
类实现了[Symbol.iterator]()
方法,该方法返回一个具有next()
方法的对象,符合迭代器协议。通过这种方式,我们可以用for...of
循环或其他支持迭代器的方法(如Array.from()
、...扩展运算符
等)来遍历这个类实例中的数据。
在Node.js环境中,由于内置的数据结构(如数组、Map、Set等)已经内置了迭代器支持,所以通常不需要手动为这些结构实现迭代器。但当需要自定义集合或其他数据结构时,可能会需要自己编写迭代器。
19-职责链模式
职责链模式(Chain of Responsibility)是一种行为设计模式,它允许你将请求沿着处理者对象链进行传递,直到有一个处理者对象能够处理这个请求。以下是一个在Node.js中实现职责链模式的例子:
// 定义抽象处理者类
class Handler {
constructor(successor) {
this.successor = successor;
}
// 处理请求的方法
handleRequest(request) {
if (this.canHandle(request)) {
return this.processRequest(request);
} else if (this.successor) {
return this.successor.handleRequest(request);
} else {
throw new Error('No handler can process the request');
}
}
// 抽象方法,子类需要实现
canHandle(request) {
throw new Error('canHandle method must be implemented in subclass');
}
// 抽象方法,子类需要实现
processRequest(request) {
throw new Error('processRequest method must be implemented in subclass');
}
}
// 具体处理者A
class ConcreteHandlerA extends Handler {
canHandle(request) {
return request.type === 'typeA';
}
processRequest(request) {
console.log(`ConcreteHandlerA is handling request: ${request.message}`);
if (/* 某种条件 */) {
// 如果能完全处理则直接返回
return;
} else {
// 否则转发给下一个处理者
return super.handleRequest(request);
}
}
}
// 具体处理者B
class ConcreteHandlerB extends Handler {
canHandle(request) {
return request.type === 'typeB';
}
processRequest(request) {
console.log(`ConcreteHandlerB is handling request: ${request.message}`);
// 在这里处理请求
}
}
// 创建职责链
const handlerA = new ConcreteHandlerA(new ConcreteHandlerB());
const requestA = { type: 'typeA', message: 'Request A' };
const requestB = { type: 'typeB', message: 'Request B' };
handlerA.handleRequest(requestA); // 输出:ConcreteHandlerA is handling request: Request A
handlerA.handleRequest(requestB); // 输出:ConcreteHandlerB is handling request: Request B
// 如果没有匹配的处理者,则抛出错误
const requestC = { type: 'typeC', message: 'Request C' };
handlerA.handleRequest(requestC); // 抛出错误:No handler can process the request
在这个例子中,Handler
是抽象处理者,定义了处理请求的基本结构。ConcreteHandlerA
和ConcreteHandlerB
是具体的处理者,它们根据请求类型来判断是否可以处理,并执行相应的操作。如果当前处理者不能处理请求,则会通过调用super.handleRequest(request)
将其传递给链中的下一个处理者。
最后求点赞,求分享,求抱抱…