JavaScript 设计模式面试题
1. 请简述什么是设计模式,以及为什么要使用设计模式
Details
设计模式是在软件设计中针对特定问题的可重用解决方案,它是经过验证的、被广泛接受的设计经验的总结。
设计模式的核心概念
- 模式:在特定环境下解决特定问题的一种方案
- 可重用:模式可以在不同的场景中重复使用
- 经过验证:模式是经过实践检验的有效解决方案
- 最佳实践:模式代表了行业内的最佳实践
为什么要使用设计模式
- 提高代码可维护性:设计模式提供了清晰的代码结构,使代码更易于理解和维护。
- 提高代码可复用性:设计模式封装了通用的解决方案,可以在不同的项目中重复使用。
- 提高代码可靠性:设计模式经过了实践的检验,能够有效解决特定问题。
- 促进团队协作:设计模式提供了共同的词汇和理解,使团队成员能够更好地沟通和协作。
- 应对变化:设计模式使代码更加灵活,能够更好地应对需求的变化。
设计模式的分类
根据《设计模式:可复用面向对象软件的基础》一书,设计模式可以分为三类:
- 创建型模式:处理对象的创建过程,如单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式。
- 结构型模式:处理对象之间的组合关系,如适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
- 行为型模式:处理对象之间的交互和职责分配,如策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
设计模式的应用原则
- 不要过度设计:只在必要时使用设计模式,避免为了使用模式而使用模式。
- 理解模式的本质:不仅要了解模式的实现,还要理解模式的适用场景和意图。
- 灵活应用:根据具体情况灵活调整模式的实现,而不是生搬硬套。
- 关注问题本身:设计模式是解决问题的工具,应该先理解问题,再选择合适的模式。
2. 请解释单例模式(Singleton Pattern),并实现一个单例模式
Details
单例模式是一种创建型模式,确保一个类只有一个实例,并提供一个全局访问点。
单例模式的核心思想
- 唯一性:确保一个类只有一个实例
- 全局访问:提供一个全局访问点来获取这个实例
- 延迟初始化:在需要时才创建实例,提高性能
单例模式的适用场景
- 全局配置:如应用程序的配置对象
- 资源共享:如数据库连接池、线程池
- 工具类:如日志记录器、缓存管理器
- 状态管理:如全局状态管理
实现方式
1. 立即执行函数表达式(IIFE)实现
javascript
const Singleton = (function() {
let instance;
function createInstance() {
// 实际的初始化代码
return {
name: 'Singleton',
method: function() {
console.log('Singleton method');
}
};
}
return {
getInstance: function() {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();
// 使用
const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();
console.log(instance1 === instance2); // true2. 类实现(ES6+)
javascript
class Singleton {
constructor() {
if (Singleton.instance) {
return Singleton.instance;
}
// 实际的初始化代码
this.name = 'Singleton';
Singleton.instance = this;
}
method() {
console.log('Singleton method');
}
static getInstance() {
if (!Singleton.instance) {
Singleton.instance = new Singleton();
}
return Singleton.instance;
}
}
// 使用
const instance1 = new Singleton();
const instance2 = new Singleton();
console.log(instance1 === instance2); // true
// 或者使用静态方法
const instance3 = Singleton.getInstance();
console.log(instance1 === instance3); // true3. 模块模式实现
javascript
// singleton.js
let instance;
function init() {
// 实际的初始化代码
return {
name: 'Singleton',
method: function() {
console.log('Singleton method');
}
};
}
export default {
getInstance: function() {
if (!instance) {
instance = init();
}
return instance;
}
};
// 使用
import Singleton from './singleton.js';
const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();
console.log(instance1 === instance2); // true单例模式的优缺点
优点
- 节省资源:只创建一个实例,避免了重复创建对象带来的资源消耗
- 全局访问:提供了一个全局访问点,方便在不同的地方使用同一个实例
- 一致性:确保所有使用该实例的代码都使用同一个对象,避免了数据不一致的问题
缺点
- 违反单一职责原则:单例类既要负责创建自己的实例,又要负责业务逻辑
- 隐藏依赖关系:使用单例模式会使代码的依赖关系不明确,降低代码的可测试性
- 可能导致内存泄漏:如果单例对象持有大量资源,且在应用程序结束前没有释放,可能会导致内存泄漏
- 难以扩展:单例模式的扩展性较差,因为它限制了类的实例化
注意事项
- 线程安全:在多线程环境下,需要确保单例的创建是线程安全的
- 序列化:如果单例类需要序列化,需要确保反序列化后仍然是同一个实例
- 全局状态:过度使用单例会导致全局状态过多,增加代码的复杂性
- 测试:单例模式会使测试变得困难,因为它难以模拟和替换
3. 请解释工厂模式(Factory Pattern),并实现一个工厂模式
Details
工厂模式是一种创建型模式,它提供了一种创建对象的接口,让子类决定实例化哪一个类。
工厂模式的核心思想
- 封装创建逻辑:将对象的创建过程封装起来,客户端不需要知道具体的创建细节
- 统一接口:通过一个统一的接口来创建不同类型的对象
- 灵活性:可以根据不同的条件创建不同类型的对象
工厂模式的分类
- 简单工厂模式:由一个工厂类根据传入的参数决定创建哪一种产品类的实例
- 工厂方法模式:定义一个创建对象的接口,让子类决定实例化哪一个类
- 抽象工厂模式:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类
简单工厂模式
实现
javascript
// 产品类
class ProductA {
operation() {
return 'Product A operation';
}
}
class ProductB {
operation() {
return 'Product B operation';
}
}
// 工厂类
class SimpleFactory {
static createProduct(type) {
switch (type) {
case 'A':
return new ProductA();
case 'B':
return new ProductB();
default:
throw new Error('Invalid product type');
}
}
}
// 使用
const productA = SimpleFactory.createProduct('A');
console.log(productA.operation()); // 'Product A operation'
const productB = SimpleFactory.createProduct('B');
console.log(productB.operation()); // 'Product B operation'工厂方法模式
实现
javascript
// 产品接口
class Product {
operation() {
throw new Error('Subclass must implement operation method');
}
}
// 具体产品
class ConcreteProductA extends Product {
operation() {
return 'ConcreteProduct A operation';
}
}
class ConcreteProductB extends Product {
operation() {
return 'ConcreteProduct B operation';
}
}
// 工厂接口
class Factory {
createProduct() {
throw new Error('Subclass must implement createProduct method');
}
}
// 具体工厂
class ConcreteFactoryA extends Factory {
createProduct() {
return new ConcreteProductA();
}
}
class ConcreteFactoryB extends Factory {
createProduct() {
return new ConcreteProductB();
}
}
// 使用
const factoryA = new ConcreteFactoryA();
const productA = factoryA.createProduct();
console.log(productA.operation()); // 'ConcreteProduct A operation'
const factoryB = new ConcreteFactoryB();
const productB = factoryB.createProduct();
console.log(productB.operation()); // 'ConcreteProduct B operation'抽象工厂模式
实现
javascript
// 产品接口
class AbstractProductA {
operation() {
throw new Error('Subclass must implement operation method');
}
}
class AbstractProductB {
operation() {
throw new Error('Subclass must implement operation method');
}
}
// 具体产品
class ConcreteProductA1 extends AbstractProductA {
operation() {
return 'ConcreteProduct A1 operation';
}
}
class ConcreteProductA2 extends AbstractProductA {
operation() {
return 'ConcreteProduct A2 operation';
}
}
class ConcreteProductB1 extends AbstractProductB {
operation() {
return 'ConcreteProduct B1 operation';
}
}
class ConcreteProductB2 extends AbstractProductB {
operation() {
return 'ConcreteProduct B2 operation';
}
}
// 抽象工厂
class AbstractFactory {
createProductA() {
throw new Error('Subclass must implement createProductA method');
}
createProductB() {
throw new Error('Subclass must implement createProductB method');
}
}
// 具体工厂
class ConcreteFactory1 extends AbstractFactory {
createProductA() {
return new ConcreteProductA1();
}
createProductB() {
return new ConcreteProductB1();
}
}
class ConcreteFactory2 extends AbstractFactory {
createProductA() {
return new ConcreteProductA2();
}
createProductB() {
return new ConcreteProductB2();
}
}
// 使用
const factory1 = new ConcreteFactory1();
const productA1 = factory1.createProductA();
const productB1 = factory1.createProductB();
console.log(productA1.operation()); // 'ConcreteProduct A1 operation'
console.log(productB1.operation()); // 'ConcreteProduct B1 operation'
const factory2 = new ConcreteFactory2();
const productA2 = factory2.createProductA();
const productB2 = factory2.createProductB();
console.log(productA2.operation()); // 'ConcreteProduct A2 operation'
console.log(productB2.operation()); // 'ConcreteProduct B2 operation'工厂模式的优缺点
优点
- 封装性:将对象的创建过程封装起来,客户端不需要知道具体的创建细节
- 灵活性:可以根据不同的条件创建不同类型的对象
- 可维护性:如果需要添加新的产品类型,只需要修改工厂类,不需要修改客户端代码
- 解耦:将产品的创建与使用分离,降低了代码的耦合度
缺点
- 增加代码复杂度:引入工厂模式会增加代码的复杂度,需要创建更多的类
- 难以扩展:如果产品类型很多,会导致工厂类变得非常复杂
- 违反开闭原则:在简单工厂模式中,添加新的产品类型需要修改工厂类的代码
适用场景
- 对象创建过程复杂:当对象的创建过程比较复杂,需要很多参数或步骤时
- 需要创建多种类型的对象:当需要根据不同的条件创建不同类型的对象时
- 解耦:当需要将对象的创建与使用分离,降低代码耦合度时
- 可维护性:当需要提高代码的可维护性和可扩展性时
4. 请解释观察者模式(Observer Pattern),并实现一个观察者模式
Details
观察者模式是一种行为型模式,它定义了对象之间的一对多依赖关系,当一个对象状态发生变化时,所有依赖它的对象都会自动收到通知并更新。
观察者模式的核心思想
- 发布-订阅:主题(Subject)负责发布通知,观察者(Observer)负责订阅通知
- 松耦合:主题和观察者之间是松耦合的,它们之间通过接口进行通信
- 自动更新:当主题状态变化时,观察者会自动收到通知并更新
观察者模式的角色
- 主题(Subject):维护一组观察者,提供添加和删除观察者的方法,以及通知观察者的方法
- 观察者(Observer):定义接收通知的接口,当收到主题的通知时进行更新
- 具体主题(Concrete Subject):实现主题接口,维护状态,并在状态变化时通知观察者
- 具体观察者(Concrete Observer):实现观察者接口,接收主题的通知并进行相应的处理
实现
javascript
// 主题类
class Subject {
constructor() {
this.observers = [];
}
// 添加观察者
addObserver(observer) {
this.observers.push(observer);
}
// 移除观察者
removeObserver(observer) {
const index = this.observers.indexOf(observer);
if (index > -1) {
this.observers.splice(index, 1);
}
}
// 通知所有观察者
notify() {
for (const observer of this.observers) {
observer.update(this);
}
}
}
// 具体主题类
class ConcreteSubject extends Subject {
constructor() {
super();
this.state = 0;
}
setState(state) {
this.state = state;
this.notify(); // 状态变化时通知观察者
}
getState() {
return this.state;
}
}
// 观察者接口
class Observer {
update(subject) {
throw new Error('Subclass must implement update method');
}
}
// 具体观察者类
class ConcreteObserverA extends Observer {
update(subject) {
console.log(`ConcreteObserverA received update: ${subject.getState()}`);
}
}
class ConcreteObserverB extends Observer {
update(subject) {
console.log(`ConcreteObserverB received update: ${subject.getState()}`);
}
}
// 使用
const subject = new ConcreteSubject();
const observerA = new ConcreteObserverA();
const observerB = new ConcreteObserverB();
subject.addObserver(observerA);
subject.addObserver(observerB);
// 改变主题状态,触发通知
subject.setState(1);
// 输出:
// ConcreteObserverA received update: 1
// ConcreteObserverB received update: 1
// 移除一个观察者
subject.removeObserver(observerA);
// 再次改变主题状态
subject.setState(2);
// 输出:
// ConcreteObserverB received update: 2观察者模式的优缺点
优点
- 松耦合:主题和观察者之间是松耦合的,它们之间通过接口进行通信
- 可扩展性:可以轻松添加新的观察者,而不需要修改主题的代码
- 自动更新:当主题状态变化时,观察者会自动收到通知并更新
- 广播通信:主题可以同时通知多个观察者,实现一对多的通信
缺点
- 内存泄漏:如果观察者没有被正确移除,可能会导致内存泄漏
- 通知顺序:观察者的通知顺序是不确定的,可能会影响业务逻辑
- 性能问题:如果观察者数量过多,可能会影响通知的性能
- 循环依赖:如果观察者和主题之间存在循环依赖,可能会导致无限循环
适用场景
- 事件处理系统:如 DOM 事件、自定义事件
- 消息队列:如发布-订阅系统
- 状态管理:如 Redux、Vuex 等状态管理库
- 数据绑定:如双向数据绑定
- 监控系统:如系统监控、性能监控
实际应用
- DOM 事件:浏览器的 DOM 事件系统就是观察者模式的典型应用
javascript
// 订阅事件
document.getElementById('button').addEventListener('click', function() {
console.log('Button clicked');
});
// 发布事件(由浏览器触发)
// 当用户点击按钮时,浏览器会触发 click 事件,通知所有订阅者- 自定义事件:
javascript
// 自定义事件发射器
class EventEmitter {
constructor() {
this.events = {};
}
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
}
off(event, callback) {
if (this.events[event]) {
this.events[event] = this.events[event].filter(cb => cb !== callback);
}
}
emit(event, ...args) {
if (this.events[event]) {
this.events[event].forEach(callback => callback(...args));
}
}
}
// 使用
const emitter = new EventEmitter();
// 订阅事件
emitter.on('message', (data) => {
console.log('Received message:', data);
});
// 发布事件
emitter.emit('message', 'Hello, world!');
// 输出:Received message: Hello, world!5. 请解释装饰器模式(Decorator Pattern),并实现一个装饰器模式
Details
装饰器模式是一种结构型模式,它允许向一个现有的对象添加新的功能,而不改变其结构。装饰器模式通过包装原始对象来实现功能的扩展。
装饰器模式的核心思想
- 动态扩展:在运行时动态地给对象添加额外的功能
- 包装:通过包装原始对象来实现功能的扩展
- 透明性:装饰器对象和原始对象具有相同的接口,客户端可以透明地使用装饰器对象
- 组合:通过组合的方式而不是继承的方式来扩展功能
装饰器模式的角色
- 组件(Component):定义对象的接口,可以给这些对象动态地添加责任
- 具体组件(Concrete Component):实现组件接口的具体对象
- 装饰器(Decorator):持有一个组件对象的引用,并实现与组件相同的接口
- 具体装饰器(Concrete Decorator):向组件添加具体的责任
实现
javascript
// 组件接口
class Component {
operation() {
throw new Error('Subclass must implement operation method');
}
}
// 具体组件
class ConcreteComponent extends Component {
operation() {
return 'ConcreteComponent operation';
}
}
// 装饰器
class Decorator extends Component {
constructor(component) {
super();
this.component = component;
}
operation() {
return this.component.operation();
}
}
// 具体装饰器 A
class ConcreteDecoratorA extends Decorator {
operation() {
return `ConcreteDecoratorA(${super.operation()})`;
}
}
// 具体装饰器 B
class ConcreteDecoratorB extends Decorator {
operation() {
return `ConcreteDecoratorB(${super.operation()})`;
}
}
// 使用
const component = new ConcreteComponent();
console.log(component.operation()); // 'ConcreteComponent operation'
// 用装饰器 A 包装
const decoratorA = new ConcreteDecoratorA(component);
console.log(decoratorA.operation()); // 'ConcreteDecoratorA(ConcreteComponent operation)'
// 用装饰器 B 包装装饰器 A
const decoratorB = new ConcreteDecoratorB(decoratorA);
console.log(decoratorB.operation()); // 'ConcreteDecoratorB(ConcreteDecoratorA(ConcreteComponent operation))'装饰器模式的优缺点
优点
- 灵活性:可以动态地给对象添加功能,而不需要修改原始对象的代码
- 可组合性:可以通过组合多个装饰器来实现复杂的功能
- 遵循开闭原则:对扩展开放,对修改关闭
- 替代继承:通过组合的方式而不是继承的方式来扩展功能,避免了继承的缺点
缺点
- 增加代码复杂度:引入装饰器模式会增加代码的复杂度,需要创建更多的类
- 嵌套层次:如果装饰器嵌套层次过深,会使代码变得难以理解和维护
- 性能影响:每次调用装饰器的方法都会调用原始对象的方法,可能会影响性能
适用场景
- 功能扩展:当需要给对象添加额外的功能,而不修改原始对象的代码时
- 动态功能:当需要在运行时动态地给对象添加功能时
- 替代继承:当使用继承会导致类的层次结构过于复杂时
- 单一职责:当需要将不同的功能分离到不同的装饰器中时
实际应用
- ES7 装饰器:
javascript
// 类装饰器
function log(target) {
const original = target;
// 创建一个新的构造函数
function construct(constructor, args) {
const c = function() {
return constructor.apply(this, args);
};
c.prototype = constructor.prototype;
return new c();
}
const newConstructor = function(...args) {
console.log(`Creating instance of ${original.name}`);
return construct(original, args);
};
newConstructor.prototype = original.prototype;
return newConstructor;
}
// 方法装饰器
function logMethod(target, name, descriptor) {
const original = descriptor.value;
descriptor.value = function(...args) {
console.log(`Calling ${name} with args: ${args}`);
const result = original.apply(this, args);
console.log(`${name} returned: ${result}`);
return result;
};
return descriptor;
}
// 使用装饰器
@log
class Person {
constructor(name) {
this.name = name;
}
@logMethod
greet() {
return `Hello, my name is ${this.name}`;
}
}
// 使用
const person = new Person('John');
// 输出:Creating instance of Person
person.greet();
// 输出:
// Calling greet with args:
// greet returned: Hello, my name is John- 中间件:
javascript
// 中间件函数
function logger(req, res, next) {
console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
next();
}
function auth(req, res, next) {
const token = req.headers.authorization;
if (token) {
console.log('Authenticated');
next();
} else {
res.status(401).send('Unauthorized');
}
}
function errorHandler(err, req, res, next) {
console.error(err);
res.status(500).send('Internal Server Error');
}
// 模拟 Express 应用
class App {
constructor() {
this.middleware = [];
}
use(fn) {
this.middleware.push(fn);
}
handle(req, res) {
let index = 0;
const next = (err) => {
if (err) {
// 找到错误处理中间件
const errorHandler = this.middleware.find(fn => fn.length === 4);
if (errorHandler) {
errorHandler(err, req, res, next);
}
return;
}
if (index < this.middleware.length) {
const fn = this.middleware[index++];
if (fn.length === 4) {
// 跳过错误处理中间件
next();
} else {
fn(req, res, next);
}
}
};
next();
}
}
// 使用
const app = new App();
app.use(logger);
app.use(auth);
app.use(errorHandler);
// 模拟请求
app.handle({ method: 'GET', url: '/api', headers: { authorization: 'token123' } }, { status: (code) => ({ send: (message) => console.log(`Response: ${code} ${message}`) }) });
// 输出:
// 2023-10-01T00:00:00.000Z - GET /api
// Authenticated6. 请解释策略模式(Strategy Pattern),并实现一个策略模式
Details
策略模式是一种行为型模式,它定义了一系列算法,并将每个算法封装起来,使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户端。
策略模式的核心思想
- 封装算法:将不同的算法封装到不同的策略类中
- 互换性:不同的策略可以相互替换,客户端可以根据需要选择不同的策略
- 分离变化:将算法的实现与使用分离,使算法的变化不影响客户端
策略模式的角色
- 策略接口(Strategy):定义所有支持的算法的公共接口
- 具体策略(Concrete Strategy):实现策略接口的具体算法
- 上下文(Context):持有一个策略对象的引用,使用策略对象来执行算法
实现
javascript
// 策略接口
class Strategy {
execute(data) {
throw new Error('Subclass must implement execute method');
}
}
// 具体策略 A
class ConcreteStrategyA extends Strategy {
execute(data) {
return `ConcreteStrategyA executed with data: ${data}`;
}
}
// 具体策略 B
class ConcreteStrategyB extends Strategy {
execute(data) {
return `ConcreteStrategyB executed with data: ${data}`;
}
}
// 上下文
class Context {
constructor(strategy) {
this.strategy = strategy;
}
setStrategy(strategy) {
this.strategy = strategy;
}
executeStrategy(data) {
return this.strategy.execute(data);
}
}
// 使用
const context = new Context(new ConcreteStrategyA());
console.log(context.executeStrategy('test')); // 'ConcreteStrategyA executed with data: test'
// 切换策略
context.setStrategy(new ConcreteStrategyB());
console.log(context.executeStrategy('test')); // 'ConcreteStrategyB executed with data: test'策略模式的优缺点
优点
- 灵活性:可以动态地切换算法,而不需要修改客户端代码
- 可扩展性:可以轻松添加新的策略,而不需要修改现有代码
- 代码复用:不同的策略可以在不同的场景中重复使用
- 分离关注点:将算法的实现与使用分离,使代码更加清晰
缺点
- 增加代码复杂度:引入策略模式会增加代码的复杂度,需要创建更多的类
- 客户端需要了解策略:客户端需要了解不同的策略,以便选择合适的策略
- 策略数量:如果策略数量过多,可能会导致代码变得难以管理
适用场景
- 多种算法选择:当有多种算法可以选择,且客户端需要根据不同的情况选择不同的算法时
- 算法频繁变化:当算法需要频繁变化,且客户端希望算法的变化不影响自己时
- 避免条件语句:当使用条件语句来选择不同的算法会导致代码变得复杂时
- 可测试性:当需要提高代码的可测试性时
实际应用
- 排序算法:
javascript
// 策略接口
class SortStrategy {
sort(array) {
throw new Error('Subclass must implement sort method');
}
}
// 具体策略:冒泡排序
class BubbleSortStrategy extends SortStrategy {
sort(array) {
const arr = [...array];
for (let i = 0; i < arr.length - 1; i++) {
for (let j = 0; j < arr.length - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
}
}
}
return arr;
}
}
// 具体策略:快速排序
class QuickSortStrategy extends SortStrategy {
sort(array) {
const arr = [...array];
if (arr.length <= 1) return arr;
const pivot = arr[Math.floor(arr.length / 2)];
const left = arr.filter(x => x < pivot);
const middle = arr.filter(x => x === pivot);
const right = arr.filter(x => x > pivot);
return [...this.sort(left), ...middle, ...this.sort(right)];
}
}
// 上下文
class Sorter {
constructor(strategy) {
this.strategy = strategy;
}
setStrategy(strategy) {
this.strategy = strategy;
}
sort(array) {
return this.strategy.sort(array);
}
}
// 使用
const sorter = new Sorter(new BubbleSortStrategy());
console.log(sorter.sort([3, 1, 4, 1, 5, 9, 2, 6])); // [1, 1, 2, 3, 4, 5, 6, 9]
// 切换策略
const sorter2 = new Sorter(new QuickSortStrategy());
console.log(sorter2.sort([3, 1, 4, 1, 5, 9, 2, 6])); // [1, 1, 2, 3, 4, 5, 6, 9]- 表单验证:
javascript
// 策略接口
class ValidationStrategy {
validate(value) {
throw new Error('Subclass must implement validate method');
}
}
// 具体策略:必填验证
class RequiredValidation extends ValidationStrategy {
validate(value) {
return value !== undefined && value !== null && value !== '';
}
}
// 具体策略:邮箱验证
class EmailValidation extends ValidationStrategy {
validate(value) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(value);
}
}
// 具体策略:长度验证
class LengthValidation extends ValidationStrategy {
constructor(min, max) {
super();
this.min = min;
this.max = max;
}
validate(value) {
const length = value.length;
return length >= this.min && length <= this.max;
}
}
// 上下文
class Validator {
constructor() {
this.strategies = {};
}
addStrategy(field, strategy) {
if (!this.strategies[field]) {
this.strategies[field] = [];
}
this.strategies[field].push(strategy);
}
validate(data) {
const errors = {};
for (const field in this.strategies) {
const value = data[field];
const fieldErrors = [];
for (const strategy of this.strategies[field]) {
if (!strategy.validate(value)) {
fieldErrors.push(`Invalid ${field}`);
}
}
if (fieldErrors.length > 0) {
errors[field] = fieldErrors;
}
}
return {
isValid: Object.keys(errors).length === 0,
errors
};
}
}
// 使用
const validator = new Validator();
// 添加验证策略
validator.addStrategy('name', new RequiredValidation());
validator.addStrategy('email', new RequiredValidation());
validator.addStrategy('email', new EmailValidation());
validator.addStrategy('password', new RequiredValidation());
validator.addStrategy('password', new LengthValidation(6, 20));
// 验证数据
const result1 = validator.validate({
name: 'John',
email: 'john@example.com',
password: 'password123'
});
console.log(result1); // { isValid: true, errors: {} }
const result2 = validator.validate({
name: '',
email: 'invalid-email',
password: '123'
});
console.log(result2);
// {
// isValid: false,
// errors: {
// name: ['Invalid name'],
// email: ['Invalid email'],
// password: ['Invalid password']
// }
// }7. 请解释模块模式(Module Pattern),并实现一个模块模式
Details
模块模式是一种设计模式,它允许将代码组织成独立的、可重用的模块,避免全局命名空间污染,提供私有变量和方法,以及公共接口。
模块模式的核心思想
- 封装:将相关的代码组织在一起,形成一个独立的模块
- 私有变量:通过闭包创建私有变量和方法,外部无法访问
- 公共接口:通过返回一个对象来暴露公共接口,外部可以访问
- 命名空间:避免全局命名空间污染,提高代码的可维护性
实现方式
1. 立即执行函数表达式(IIFE)实现
javascript
const Module = (function() {
// 私有变量
let privateVariable = 'private';
// 私有方法
function privateMethod() {
console.log('Private method');
return privateVariable;
}
// 公共接口
return {
publicVariable: 'public',
publicMethod: function() {
console.log('Public method');
// 可以访问私有变量和方法
console.log(privateMethod());
return this.publicVariable;
},
setPrivateVariable: function(value) {
privateVariable = value;
}
};
})();
// 使用
console.log(Module.publicVariable); // 'public'
console.log(Module.publicMethod()); // 'Public method' -> 'Private method' -> 'private' -> 'public'
// 尝试访问私有变量(会失败)
console.log(Module.privateVariable); // undefined
// 尝试调用私有方法(会失败)
// console.log(Module.privateMethod()); // TypeError: Module.privateMethod is not a function
// 通过公共方法修改私有变量
Module.setPrivateVariable('modified private');
console.log(Module.publicMethod()); // 'Public method' -> 'Private method' -> 'modified private' -> 'public'2. 带参数的 IIFE 实现
javascript
const Module = (function(dependency) {
// 私有变量
let privateVariable = 'private';
// 私有方法
function privateMethod() {
console.log('Private method');
return privateVariable;
}
// 公共接口
return {
publicVariable: 'public',
publicMethod: function() {
console.log('Public method');
console.log(privateMethod());
// 使用依赖
console.log(`Using dependency: ${dependency}`);
return this.publicVariable;
}
};
})('dependency value');
// 使用
console.log(Module.publicMethod());
// 输出:
// Public method
// Private method
// private
// Using dependency: dependency value
// public3. ES6 模块实现
javascript
// module.js
// 私有变量
let privateVariable = 'private';
// 私有方法
function privateMethod() {
console.log('Private method');
return privateVariable;
}
// 公共接口
export const publicVariable = 'public';
export function publicMethod() {
console.log('Public method');
console.log(privateMethod());
return publicVariable;
}
export function setPrivateVariable(value) {
privateVariable = value;
}
// 使用
import { publicVariable, publicMethod, setPrivateVariable } from './module.js';
console.log(publicVariable); // 'public'
console.log(publicMethod()); // 'Public method' -> 'Private method' -> 'private' -> 'public'
// 尝试访问私有变量(会失败)
// console.log(privateVariable); // ReferenceError: privateVariable is not defined
// 尝试调用私有方法(会失败)
// console.log(privateMethod()); // ReferenceError: privateMethod is not defined
// 通过公共方法修改私有变量
setPrivateVariable('modified private');
console.log(publicMethod()); // 'Public method' -> 'Private method' -> 'modified private' -> 'public'模块模式的优缺点
优点
- 封装性:将相关的代码组织在一起,形成一个独立的模块
- 私有变量:通过闭包创建私有变量和方法,外部无法访问,提高了代码的安全性
- 公共接口:通过返回一个对象来暴露公共接口,外部可以访问,提供了清晰的 API
- 命名空间:避免全局命名空间污染,提高代码的可维护性
- 依赖管理:可以通过参数传递依赖,提高代码的可测试性和可维护性
缺点
- 闭包开销:使用闭包会增加内存开销,因为闭包会保留对外部变量的引用
- 模块大小:如果模块过大,可能会影响性能
- 调试困难:私有变量和方法无法在外部直接访问,增加了调试的难度
- 灵活性:一旦模块被创建,其公共接口就固定了,难以动态修改
适用场景
- 代码组织:当需要将相关的代码组织在一起,形成一个独立的模块时
- 封装:当需要封装私有变量和方法,只暴露公共接口时
- 命名空间:当需要避免全局命名空间污染时
- 依赖管理:当需要管理模块之间的依赖关系时
实际应用
- 工具库:
javascript
const Utils = (function() {
// 私有方法
function validateEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
function validatePhone(phone) {
const phoneRegex = /^\d{10}$/;
return phoneRegex.test(phone);
}
// 公共接口
return {
validate: function(type, value) {
switch (type) {
case 'email':
return validateEmail(value);
case 'phone':
return validatePhone(value);
default:
return false;
}
},
formatCurrency: function(amount) {
return `$${amount.toFixed(2)}`;
},
formatDate: function(date) {
return new Date(date).toLocaleDateString();
}
};
})();
// 使用
console.log(Utils.validate('email', 'john@example.com')); // true
console.log(Utils.validate('phone', '1234567890')); // true
console.log(Utils.formatCurrency(100)); // '$100.00'
console.log(Utils.formatDate(new Date())); // 2023/10/01- 单例服务:
javascript
const ApiService = (function() {
// 私有变量
const baseUrl = 'https://api.example.com';
let token = null;
// 私有方法
function getHeaders() {
const headers = {
'Content-Type': 'application/json'
};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
return headers;
}
// 公共接口
return {
setToken: function(newToken) {
token = newToken;
},
get: async function(endpoint) {
const response = await fetch(`${baseUrl}${endpoint}`, {
method: 'GET',
headers: getHeaders()
});
return response.json();
},
post: async function(endpoint, data) {
const response = await fetch(`${baseUrl}${endpoint}`, {
method: 'POST',
headers: getHeaders(),
body: JSON.stringify(data)
});
return response.json();
},
put: async function(endpoint, data) {
const response = await fetch(`${baseUrl}${endpoint}`, {
method: 'PUT',
headers: getHeaders(),
body: JSON.stringify(data)
});
return response.json();
},
delete: async function(endpoint) {
const response = await fetch(`${baseUrl}${endpoint}`, {
method: 'DELETE',
headers: getHeaders()
});
return response.json();
}
};
})();
// 使用
ApiService.setToken('token123');
ApiService.get('/users')
.then(data => console.log(data))
.catch(error => console.error(error));8. 请解释代理模式(Proxy Pattern),并实现一个代理模式
Details
代理模式是一种结构型模式,它为其他对象提供一种代理以控制对这个对象的访问。代理模式可以在不修改原始对象的情况下,为其添加额外的功能。
代理模式的核心思想
- 控制访问:通过代理对象控制对原始对象的访问
- 额外功能:在访问原始对象之前或之后添加额外的功能
- 透明性:代理对象和原始对象具有相同的接口,客户端可以透明地使用代理对象
代理模式的角色
- 抽象主题(Subject):定义代理对象和原始对象的共同接口
- 真实主题(Real Subject):实现抽象主题接口,是代理对象所代表的真实对象
- 代理(Proxy):实现抽象主题接口,持有真实主题的引用,控制对真实主题的访问
实现
javascript
// 抽象主题
class Subject {
request() {
throw new Error('Subclass must implement request method');
}
}
// 真实主题
class RealSubject extends Subject {
request() {
console.log('RealSubject: Handling request');
return 'RealSubject response';
}
}
// 代理
class Proxy extends Subject {
constructor(realSubject) {
super();
this.realSubject = realSubject;
}
request() {
// 在访问真实主题之前添加额外的功能
this.beforeRequest();
// 调用真实主题的方法
const result = this.realSubject.request();
// 在访问真实主题之后添加额外的功能
this.afterRequest();
return result;
}
beforeRequest() {
console.log('Proxy: Before handling request');
}
afterRequest() {
console.log('Proxy: After handling request');
}
}
// 使用
const realSubject = new RealSubject();
const proxy = new Proxy(realSubject);
console.log(proxy.request());
// 输出:
// Proxy: Before handling request
// RealSubject: Handling request
// Proxy: After handling request
// RealSubject response代理模式的类型
- 远程代理:为远程对象提供代理,如网络服务的代理
- 虚拟代理:延迟创建开销大的对象,如图片懒加载
- 保护代理:控制对原始对象的访问权限,如权限控制
- 缓存代理:为开销大的操作结果提供缓存,如计算结果缓存
- 智能引用代理:在访问原始对象时执行额外的操作,如计数引用
实际应用
- 虚拟代理(图片懒加载):
javascript
// 真实主题:图片加载
class RealImage {
constructor(src) {
this.src = src;
this.loadImage();
}
loadImage() {
console.log(`Loading image from ${this.src}`);
// 实际的图片加载逻辑
}
display() {
console.log(`Displaying image from ${this.src}`);
// 实际的图片显示逻辑
}
}
// 代理:虚拟代理,延迟加载图片
class ImageProxy {
constructor(src) {
this.src = src;
this.realImage = null;
}
display() {
if (!this.realImage) {
// 延迟创建真实图片对象
this.realImage = new RealImage(this.src);
}
this.realImage.display();
}
}
// 使用
const image = new ImageProxy('https://example.com/image.jpg');
// 第一次调用 display() 时,会创建 RealImage 实例并加载图片
console.log('First display:');
image.display();
// 输出:
// Loading image from https://example.com/image.jpg
// Displaying image from https://example.com/image.jpg
// 第二次调用 display() 时,直接使用已创建的 RealImage 实例
console.log('\nSecond display:');
image.display();
// 输出:
// Displaying image from https://example.com/image.jpg- 缓存代理(计算结果缓存):
javascript
// 真实主题:计算
class Calculator {
add(a, b) {
console.log(`Calculating ${a} + ${b}`);
// 模拟复杂计算
for (let i = 0; i < 100000000; i++) {
// 空循环,模拟计算开销
}
return a + b;
}
}
// 代理:缓存代理,缓存计算结果
class CalculatorProxy {
constructor(calculator) {
this.calculator = calculator;
this.cache = {};
}
add(a, b) {
const key = `${a}+${b}`;
if (this.cache[key]) {
console.log(`Using cached result for ${a} + ${b}`);
return this.cache[key];
}
const result = this.calculator.add(a, b);
this.cache[key] = result;
return result;
}
}
// 使用
const calculator = new Calculator();
const proxy = new CalculatorProxy(calculator);
// 第一次计算,会执行实际的计算
console.log('First calculation:');
console.log(proxy.add(10, 20)); // 30
// 第二次计算相同的参数,使用缓存
console.log('\nSecond calculation:');
console.log(proxy.add(10, 20)); // 30- ES6 Proxy:
ES6 提供了原生的 Proxy 对象,可以更方便地实现代理模式。
javascript
// 原始对象
const target = {
name: 'John',
age: 30
};
// 代理对象
const proxy = new Proxy(target, {
// 拦截获取属性的操作
get(target, property) {
console.log(`Getting property ${property}`);
return target[property];
},
// 拦截设置属性的操作
set(target, property, value) {
console.log(`Setting property ${property} to ${value}`);
target[property] = value;
return true;
},
// 拦截删除属性的操作
deleteProperty(target, property) {
console.log(`Deleting property ${property}`);
delete target[property];
return true;
},
// 拦截 in 操作符
has(target, property) {
console.log(`Checking if ${property} exists`);
return property in target;
},
// 拦截 Object.getOwnPropertyNames() 等操作
ownKeys(target) {
console.log('Getting own keys');
return Object.keys(target);
}
});
// 使用代理对象
console.log(proxy.name); // Getting property name -> 'John'
proxy.age = 31; // Setting property age to 31
console.log('age' in proxy); // Checking if age exists -> true
delete proxy.name; // Deleting property name
console.log(Object.keys(proxy)); // Getting own keys -> ['age']代理模式的优缺点
优点
- 控制访问:可以控制对原始对象的访问,如权限控制、延迟加载等
- 额外功能:可以在访问原始对象之前或之后添加额外的功能,如日志记录、缓存等
- 透明性:代理对象和原始对象具有相同的接口,客户端可以透明地使用代理对象
- 解耦:代理模式将原始对象与额外功能分离,降低了代码的耦合度
缺点
- 增加代码复杂度:引入代理模式会增加代码的复杂度,需要创建额外的代理类
- 性能开销:代理模式会增加额外的调用层次,可能会影响性能
- 维护成本:如果原始对象的接口发生变化,代理对象也需要相应地修改
适用场景
- 远程访问:当需要访问远程对象时,如网络服务的代理
- 延迟加载:当需要延迟创建开销大的对象时,如图片懒加载
- 权限控制:当需要控制对原始对象的访问权限时,如保护代理
- 缓存:当需要为开销大的操作结果提供缓存时,如计算结果缓存
- 日志记录:当需要记录对原始对象的访问时,如访问日志
- 事务管理:当需要在访问原始对象时添加事务管理时,如数据库操作的事务
9. 请解释命令模式(Command Pattern),并实现一个命令模式
Details
命令模式是一种行为型模式,它将请求封装为对象,使请求的发送者和接收者解耦。命令模式允许将请求参数化,队列化,以及支持可撤销操作。
命令模式的核心思想
- 封装请求:将请求封装为一个对象,包含执行该请求所需的所有信息
- 解耦:将请求的发送者和接收者解耦,发送者不需要知道接收者的具体实现
- 可扩展性:可以轻松添加新的命令,而不需要修改现有代码
- 支持撤销:可以实现命令的撤销和重做操作
命令模式的角色
- 命令(Command):定义执行操作的接口
- 具体命令(Concrete Command):实现命令接口,包含接收者的引用,并调用接收者的方法来执行请求
- 接收者(Receiver):执行命令所对应的操作,是命令的实际执行者
- 调用者(Invoker):持有命令对象,并在适当的时候调用命令的执行方法
- 客户端(Client):创建具体命令对象,并设置其接收者
实现
javascript
// 接收者
class Receiver {
action() {
console.log('Receiver: Performing action');
}
undoAction() {
console.log('Receiver: Undoing action');
}
}
// 命令接口
class Command {
execute() {
throw new Error('Subclass must implement execute method');
}
undo() {
throw new Error('Subclass must implement undo method');
}
}
// 具体命令
class ConcreteCommand extends Command {
constructor(receiver) {
super();
this.receiver = receiver;
}
execute() {
console.log('ConcreteCommand: Executing command');
this.receiver.action();
}
undo() {
console.log('ConcreteCommand: Undoing command');
this.receiver.undoAction();
}
}
// 调用者
class Invoker {
constructor() {
this.command = null;
this.history = [];
}
setCommand(command) {
this.command = command;
}
executeCommand() {
if (this.command) {
this.command.execute();
this.history.push(this.command);
}
}
undo() {
if (this.history.length > 0) {
const command = this.history.pop();
command.undo();
}
}
}
// 使用
const receiver = new Receiver();
const command = new ConcreteCommand(receiver);
const invoker = new Invoker();
invoker.setCommand(command);
invoker.executeCommand();
// 输出:
// ConcreteCommand: Executing command
// Receiver: Performing action
invoker.undo();
// 输出:
// ConcreteCommand: Undoing command
// Receiver: Undoing action命令模式的优缺点
优点
- 解耦:将请求的发送者和接收者解耦,发送者不需要知道接收者的具体实现
- 可扩展性:可以轻松添加新的命令,而不需要修改现有代码
- 支持撤销:可以实现命令的撤销和重做操作
- 支持队列:可以将命令排队执行,实现批处理
- 支持日志:可以记录命令的执行历史,支持恢复操作
缺点
- 增加代码复杂度:引入命令模式会增加代码的复杂度,需要创建更多的类
- 命令对象膨胀:如果有很多命令,会导致命令对象数量激增
- 性能开销:命令模式会增加额外的调用层次,可能会影响性能
适用场景
- 需要将请求参数化:当需要将请求封装为对象,以便传递、存储或排队时
- 需要支持撤销操作:当需要支持命令的撤销和重做操作时
- 需要支持队列:当需要将命令排队执行,实现批处理时
- 需要解耦:当需要将请求的发送者和接收者解耦时
- 需要日志记录:当需要记录命令的执行历史,支持恢复操作时
实际应用
- 遥控器:
javascript
// 接收者:电视
class TV {
turnOn() {
console.log('TV: Turned on');
}
turnOff() {
console.log('TV: Turned off');
}
setChannel(channel) {
console.log(`TV: Set channel to ${channel}`);
this.channel = channel;
}
getChannel() {
return this.channel || 1;
}
}
// 命令接口
class Command {
execute() {
throw new Error('Subclass must implement execute method');
}
undo() {
throw new Error('Subclass must implement undo method');
}
}
// 具体命令:打开电视
class TurnOnCommand extends Command {
constructor(tv) {
super();
this.tv = tv;
}
execute() {
this.tv.turnOn();
}
undo() {
this.tv.turnOff();
}
}
// 具体命令:关闭电视
class TurnOffCommand extends Command {
constructor(tv) {
super();
this.tv = tv;
}
execute() {
this.tv.turnOff();
}
undo() {
this.tv.turnOn();
}
}
// 具体命令:设置频道
class SetChannelCommand extends Command {
constructor(tv, channel) {
super();
this.tv = tv;
this.channel = channel;
this.previousChannel = tv.getChannel();
}
execute() {
this.tv.setChannel(this.channel);
}
undo() {
this.tv.setChannel(this.previousChannel);
}
}
// 调用者:遥控器
class RemoteControl {
constructor() {
this.commands = {};
this.history = [];
}
setCommand(button, command) {
this.commands[button] = command;
}
pressButton(button) {
if (this.commands[button]) {
this.commands[button].execute();
this.history.push(this.commands[button]);
}
}
pressUndo() {
if (this.history.length > 0) {
const command = this.history.pop();
command.undo();
}
}
}
// 使用
const tv = new TV();
const remote = new RemoteControl();
// 设置命令
remote.setCommand('on', new TurnOnCommand(tv));
remote.setCommand('off', new TurnOffCommand(tv));
remote.setCommand('channel1', new SetChannelCommand(tv, 1));
remote.setCommand('channel2', new SetChannelCommand(tv, 2));
// 执行命令
remote.pressButton('on'); // TV: Turned on
remote.pressButton('channel1'); // TV: Set channel to 1
remote.pressButton('channel2'); // TV: Set channel to 2
// 撤销操作
remote.pressUndo(); // TV: Set channel to 1
remote.pressUndo(); // TV: Turned off- 任务队列:
javascript
// 接收者:任务执行器
class TaskExecutor {
executeTask(task) {
console.log(`Executing task: ${task.name}`);
// 实际执行任务的逻辑
return `Task ${task.name} executed`;
}
}
// 命令接口
class TaskCommand {
execute() {
throw new Error('Subclass must implement execute method');
}
}
// 具体命令:任务命令
class ConcreteTaskCommand extends TaskCommand {
constructor(executor, task) {
super();
this.executor = executor;
this.task = task;
}
execute() {
return this.executor.executeTask(this.task);
}
}
// 调用者:任务队列
class TaskQueue {
constructor() {
this.queue = [];
this.executor = new TaskExecutor();
}
addTask(task) {
const command = new ConcreteTaskCommand(this.executor, task);
this.queue.push(command);
}
processQueue() {
while (this.queue.length > 0) {
const command = this.queue.shift();
command.execute();
}
}
}
// 使用
const queue = new TaskQueue();
// 添加任务
queue.addTask({ name: 'Task 1' });
queue.addTask({ name: 'Task 2' });
queue.addTask({ name: 'Task 3' });
// 处理队列
queue.processQueue();
// 输出:
// Executing task: Task 1
// Executing task: Task 2
// Executing task: Task 310. 请解释责任链模式(Chain of Responsibility Pattern),并实现一个责任链模式
Details
责任链模式是一种行为型模式,它将请求的发送者和接收者解耦,通过将多个处理对象连成一条链,使请求在链上传递,直到有一个处理对象能够处理该请求。
责任链模式的核心思想
- 解耦:将请求的发送者和接收者解耦,发送者不需要知道具体的接收者
- 链式传递:将多个处理对象连成一条链,请求在链上传递
- 自动处理:请求会自动在链上传递,直到有一个处理对象能够处理它
- 灵活性:可以动态地调整链的结构,添加或删除处理对象
责任链模式的角色
- 抽象处理者(Handler):定义处理请求的接口,包含一个指向下一个处理者的引用
- 具体处理者(Concrete Handler):实现抽象处理者接口,处理它能够处理的请求,否则将请求传递给下一个处理者
- 客户端(Client):创建处理链,并向链的第一个处理者发送请求
实现
javascript
// 抽象处理者
class Handler {
constructor() {
this.nextHandler = null;
}
setNext(handler) {
this.nextHandler = handler;
return handler;
}
handle(request) {
if (this.nextHandler) {
return this.nextHandler.handle(request);
}
return null;
}
}
// 具体处理者 A
class ConcreteHandlerA extends Handler {
handle(request) {
if (request === 'A') {
console.log('ConcreteHandlerA: Handling request A');
return 'Handled by A';
} else {
return super.handle(request);
}
}
}
// 具体处理者 B
class ConcreteHandlerB extends Handler {
handle(request) {
if (request === 'B') {
console.log('ConcreteHandlerB: Handling request B');
return 'Handled by B';
} else {
return super.handle(request);
}
}
}
// 具体处理者 C
class ConcreteHandlerC extends Handler {
handle(request) {
if (request === 'C') {
console.log('ConcreteHandlerC: Handling request C');
return 'Handled by C';
} else {
return super.handle(request);
}
}
}
// 使用
const handlerA = new ConcreteHandlerA();
const handlerB = new ConcreteHandlerB();
const handlerC = new ConcreteHandlerC();
// 设置责任链
handlerA.setNext(handlerB).setNext(handlerC);
// 处理请求
console.log(handlerA.handle('A')); // ConcreteHandlerA: Handling request A -> 'Handled by A'
console.log(handlerA.handle('B')); // ConcreteHandlerB: Handling request B -> 'Handled by B'
console.log(handlerA.handle('C')); // ConcreteHandlerC: Handling request C -> 'Handled by C'
console.log(handlerA.handle('D')); // null (没有处理者能够处理)责任链模式的优缺点
优点
- 解耦:将请求的发送者和接收者解耦,发送者不需要知道具体的接收者
- 灵活性:可以动态地调整链的结构,添加或删除处理对象
- 可扩展性:可以轻松添加新的处理者,而不需要修改现有代码
- 单一职责:每个处理者只负责处理自己能够处理的请求,符合单一职责原则
缺点
- 请求可能不被处理:如果链上没有处理者能够处理请求,请求会被丢弃
- 性能开销:请求需要在链上传递,可能会影响性能
- 调试困难:如果链的结构复杂,调试会变得困难
- 可能导致循环引用:如果链的结构设计不当,可能会导致循环引用
适用场景
- 多个对象处理同一请求:当有多个对象可以处理同一请求,且具体处理者由运行时决定时
- 动态处理链:当需要动态地调整处理链的结构时
- 避免硬编码:当需要避免在代码中硬编码处理逻辑时
- 分层处理:当需要对请求进行分层处理时
实际应用
- 日志系统:
javascript
// 抽象处理者
class Logger {
constructor(level) {
this.level = level;
this.nextLogger = null;
}
setNext(logger) {
this.nextLogger = logger;
return logger;
}
log(message, level) {
if (this.canHandle(level)) {
this.write(message);
}
if (this.nextLogger) {
this.nextLogger.log(message, level);
}
}
canHandle(level) {
return this.level <= level;
}
write(message) {
throw new Error('Subclass must implement write method');
}
}
// 具体处理者:控制台日志
class ConsoleLogger extends Logger {
write(message) {
console.log(`[CONSOLE] ${message}`);
}
}
// 具体处理者:文件日志
class FileLogger extends Logger {
write(message) {
console.log(`[FILE] ${message}`);
// 实际写入文件的逻辑
}
}
// 具体处理者:错误日志
class ErrorLogger extends Logger {
write(message) {
console.log(`[ERROR] ${message}`);
// 实际写入错误日志的逻辑
}
}
// 日志级别
const LOG_LEVEL = {
INFO: 1,
DEBUG: 2,
ERROR: 3
};
// 使用
const consoleLogger = new ConsoleLogger(LOG_LEVEL.INFO);
const fileLogger = new FileLogger(LOG_LEVEL.DEBUG);
const errorLogger = new ErrorLogger(LOG_LEVEL.ERROR);
// 设置责任链
consoleLogger.setNext(fileLogger).setNext(errorLogger);
// 记录日志
consoleLogger.log('This is an info message', LOG_LEVEL.INFO);
// 输出:
// [CONSOLE] This is an info message
// [FILE] This is an info message
// [ERROR] This is an info message
consoleLogger.log('This is a debug message', LOG_LEVEL.DEBUG);
// 输出:
// [FILE] This is a debug message
// [ERROR] This is a debug message
consoleLogger.log('This is an error message', LOG_LEVEL.ERROR);
// 输出:
// [ERROR] This is an error message- 表单验证:
javascript
// 抽象处理者
class Validator {
constructor() {
this.nextValidator = null;
}
setNext(validator) {
this.nextValidator = validator;
return validator;
}
validate(data) {
const result = this.check(data);
if (result.isValid && this.nextValidator) {
return this.nextValidator.validate(data);
}
return result;
}
check(data) {
throw new Error('Subclass must implement check method');
}
}
// 具体处理者:必填验证
class RequiredValidator extends Validator {
constructor(field) {
super();
this.field = field;
}
check(data) {
if (!data[this.field]) {
return {
isValid: false,
error: `${this.field} is required`
};
}
return { isValid: true };
}
}
// 具体处理者:邮箱验证
class EmailValidator extends Validator {
check(data) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(data.email)) {
return {
isValid: false,
error: 'Invalid email format'
};
}
return { isValid: true };
}
}
// 具体处理者:密码长度验证
class PasswordLengthValidator extends Validator {
constructor(minLength) {
super();
this.minLength = minLength;
}
check(data) {
if (data.password.length < this.minLength) {
return {
isValid: false,
error: `Password must be at least ${this.minLength} characters long`
};
}
return { isValid: true };
}
}
// 使用
const requiredValidator = new RequiredValidator('email');
const emailValidator = new EmailValidator();
const passwordValidator = new PasswordLengthValidator(6);
// 设置责任链
requiredValidator.setNext(emailValidator).setNext(passwordValidator);
// 验证数据
const result1 = requiredValidator.validate({
email: 'john@example.com',
password: 'password123'
});
console.log(result1); // { isValid: true }
const result2 = requiredValidator.validate({
email: '',
password: '123'
});
console.log(result2); // { isValid: false, error: 'email is required' }
const result3 = requiredValidator.validate({
email: 'invalid-email',
password: '123'
});
console.log(result3); // { isValid: false, error: 'Invalid email format' }
const result4 = requiredValidator.validate({
email: 'john@example.com',
password: '123'
});
console.log(result4); // { isValid: false, error: 'Password must be at least 6 characters long' }- 中间件:
javascript
// 抽象处理者
class Middleware {
constructor() {
this.nextMiddleware = null;
}
setNext(middleware) {
this.nextMiddleware = middleware;
return middleware;
}
handle(req, res, next) {
this.process(req, res, () => {
if (this.nextMiddleware) {
this.nextMiddleware.handle(req, res, next);
} else if (next) {
next();
}
});
}
process(req, res, next) {
throw new Error('Subclass must implement process method');
}
}
// 具体处理者:日志中间件
class LoggerMiddleware extends Middleware {
process(req, res, next) {
console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
next();
}
}
// 具体处理者:认证中间件
class AuthMiddleware extends Middleware {
process(req, res, next) {
const token = req.headers.authorization;
if (token) {
console.log('Authenticated');
next();
} else {
res.status(401).send('Unauthorized');
}
}
}
// 具体处理者:错误处理中间件
class ErrorHandlerMiddleware extends Middleware {
process(req, res, next) {
try {
next();
} catch (error) {
console.error(error);
res.status(500).send('Internal Server Error');
}
}
}
// 使用
const loggerMiddleware = new LoggerMiddleware();
const authMiddleware = new AuthMiddleware();
const errorHandlerMiddleware = new ErrorHandlerMiddleware();
// 设置责任链
loggerMiddleware.setNext(authMiddleware).setNext(errorHandlerMiddleware);
// 模拟请求
const req = {
method: 'GET',
url: '/api',
headers: { authorization: 'token123' }
};
const res = {
status: (code) => ({ send: (message) => console.log(`Response: ${code} ${message}`) })
};
// 处理请求
loggerMiddleware.handle(req, res);
// 输出:
// 2023-10-01T00:00:00.000Z - GET /api
// Authenticated