js设计模式

256 阅读14分钟

策略模式(Strategy Pattern)

策略模式(Strategy Pattern)是一种行为设计模式,它允许你定义一系列算法,并将每个算法封装到独立的类中,使它们可以互相替换。策略模式使得算法可以独立于使用它的客户端而变化。

关键点

  1. 策略接口(Strategy Interface) :定义了所有支持的算法的通用方法(如 calculate 方法)。
  2. 具体策略类(Concrete Strategies) :实现了策略接口,并定义了具体的算法。
  3. 环境类(Context) :持有一个策略类的引用,可以动态地更换策略。

优点

  • 可以在不修改客户端代码的情况下增加新的策略。
  • 可以避免使用条件语句来选择算法。

缺点

  • 客户端必须知道所有的策略类,并自行决定使用哪个策略。
  • 会增加类的数量。

demo

// 定义策略接口
class Strategy {
    calculate(price) {
        throw new Error("This method should be overwritten!");
    }
}

// 具体策略1:正常价格
class NormalPrice extends Strategy {
    calculate(price) {
        return price;
    }
}

// 具体策略2:打折价格
class DiscountPrice extends Strategy {
    constructor(discount) {
        super();
        this.discount = discount;
    }

    calculate(price) {
        return price * this.discount;
    }
}

// 具体策略3:满减价格
class FullReductionPrice extends Strategy {
    constructor(threshold, reduction) {
        super();
        this.threshold = threshold;
        this.reduction = reduction;
    }

    calculate(price) {
        return price >= this.threshold ? price - this.reduction : price;
    }
}

// 环境类:购物车
class ShoppingCart {
    constructor(strategy) {
        this.strategy = strategy;
    }

    setStrategy(strategy) {
        this.strategy = strategy;
    }

    calculateTotal(price) {
        return this.strategy.calculate(price);
    }
}

// 使用示例
const price = 100;
const normalPrice = new NormalPrice();
const discountPrice = new DiscountPrice(0.9);
const fullReductionPrice = new FullReductionPrice(100, 10);

const cart = new ShoppingCart(normalPrice);
console.log(cart.calculateTotal(price)); // 正常价格: 100

cart.setStrategy(discountPrice);
console.log(cart.calculateTotal(price)); // 打折价格: 90

cart.setStrategy(fullReductionPrice);
console.log(cart.calculateTotal(price)); // 满减价格: 90

代理模式(Proxy Pattern)

代理模式(Proxy Pattern)是一种结构型设计模式,主要用于在访问对象时提供一种代理,以控制对对象的访问。这种模式常见的用途包括延迟初始化、访问控制、日志记录和其他任务。代理模式可以分为以下几种类型:

  1. 远程代理(Remote Proxy):  负责处理对位于不同地址空间的对象的访问。
  2. 虚拟代理(Virtual Proxy):  控制对耗资源的对象的访问,可以通过延迟初始化(即仅在真正需要时才创建对象)来优化性能。
  3. 保护代理(Protection Proxy):  控制对对象的访问权限,可以在访问前进行权限验证。
  4. 智能引用代理(Smart Reference Proxy):  在访问对象时进行一些额外的操作,比如引用计数、日志记录等。

代理模式的优点:

  1. 控制对原对象的访问。
  2. 可以在不改变客户端代码的情况下添加额外的功能(如延迟加载、权限控制等)。
  3. 提高了系统的可扩展性和灵活性。

代理模式的缺点:

  1. 增加了系统的复杂性。
  2. 可能会导致请求处理的速度变慢(特别是远程代理)。

代理模式在实际开发中有广泛的应用,比如 JavaScript 中的 Proxy 对象,Spring 框架中的 AOP 代理等。

demo

// 假设我们有一个大型图像对象
class Image {
    constructor(filename) {
        this.filename = filename;
        this.loadFromDisk();
    }

    loadFromDisk() {
        console.log(`Loading ${this.filename} from disk...`);
    }

    display() {
        console.log(`Displaying ${this.filename}`);
    }
}

// 现在我们创建一个虚拟代理类来延迟加载图像
class ProxyImage {
    constructor(filename) {
        this.filename = filename;
        this.realImage = null;
    }

    display() {
        if (this.realImage === null) {
            this.realImage = new Image(this.filename);
        }
        this.realImage.display();
    }
}

// 使用示例
const image = new ProxyImage('test_image.jpg');
image.display();  // 第一次调用会加载并显示图像
image.display();  // 第二次调用将直接显示图像,不再加载

迭代器模式

迭代器模式是一种行为设计模式,它允许你顺序访问集合的元素,而无需暴露其底层表示。迭代器模式将集合的遍历行为从集合中分离出来,从而简化和增强集合的操作。

以下是一个用 JavaScript 实现迭代器模式的简单示例:

//迭代器
//定义一个集合类
class Aggregate {
	constructor(items) {
		this.items = items
	}

	createIterator() {
		return new Iterator(this)
	}
}

//定义一个迭代器
class Iterator {
	constructor(aggregate) {
		this.aggregate = aggregate
		this.index = 0
	}

	hasNext() {
		return this.index < this.aggregate.items.length
	}

	next() {
		return this.hasNext() ? this.aggregate.items[this.index++] : null
	}
}

// 创建一个集合
const aggregate = new Aggregate([1, 2, 3, 4, 5])

// 创建一个迭代器
const iterator = aggregate.createIterator()

while (iterator.hasNext()) {
	console.log(iterator.next())
}

有时候,你可能需要更灵活的迭代器。例如,一个迭代器不仅能遍历数组,还能遍历对象的属性。

  1. 定义一个自定义迭代器函数
//自定义一个迭代器
function createIterator(items) {
	let i = 0

	return {
		next: function () {
			return i < items.length ? { value: items[i++], done: false } : { done: true }
		},
	}
}

//使用自定义迭代器
const items = ['a', 'b', 'c']
const iterator = createIterator(items)

console.log(iterator.next()) // { value: 'a', done: false }
console.log(iterator.next()) // { value: 'b', done: false }
console.log(iterator.next()) // { value: 'c', done: false }
console.log(iterator.next()) // { done: true }

通过生成器实现迭代器

//通过生成器实现迭代器
//生成器(Generators)是 ES6 中引入的一种功能,它让你能轻松创建迭代器。
//定义一个生成器函数
function* generator(items) {
	for (let i = 0; i < items.length; i++) {
		yield items[i]
	}
}

//使用生成器
const items = ['x', 'y', 'z'];
const iterator = generator(items);

console.log(iterator.next()); // { value: 'x', done: false }
console.log(iterator.next()); // { value: 'y', done: false }
console.log(iterator.next()); // { value: 'z', done: false }
console.log(iterator.next()); // { done: true }

发布订阅模式(Publish-Subscribe Pattern)

发布订阅模式(Publish-Subscribe Pattern),也称为观察者模式(Observer Pattern),是一种软件设计模式,它定义了对象之间的一种一对多的依赖关系。当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知并自动更新。这个模式在需要广泛通知的事件处理系统中非常有用。

//发布订阅模式
// 定义一个事件中心
class EventEmitter {
	constructor() {
		this.events = {}
	}

	// 订阅事件
	on(event, listener) {
		if (!this.events[event]) {
			this.events[event] = []
		}
		this.events[event].push(listener)
	}

	// 取消订阅
	off(event, listenerToRemove) {
		if (!this.events[event]) return

		this.events[event] = this.events[event].filter(listener => listener !== listenerToRemove)
	}

	// 发布事件
	emit(event, data) {
		if (!this.events[event]) return

		this.events[event].forEach(listener => listener(data))
	}
}

// 使用示例
const eventEmitter = new EventEmitter()

const onUserLogin = user => {
	console.log(`User logged in: ${user.name}`)
}

// 订阅事件
eventEmitter.on('userLogin', onUserLogin)

// 发布事件
eventEmitter.emit('userLogin', { name: 'John Doe' })

// 取消订阅事件
eventEmitter.off('userLogin', onUserLogin)

// 发布事件,但不会有任何输出,因为订阅已经被取消
eventEmitter.emit('userLogin', { name: 'John Doe' })

在上面的示例中:

  1. EventEmitter 类实现了发布订阅模式。它包含了三个方法:

    • on(event, listener):用于订阅事件。
    • off(event, listener):用于取消订阅事件。
    • emit(event, data):用于发布事件。
  2. 我们创建了一个 EventEmitter 实例 eventEmitter,并定义了一个事件监听器 onUserLogin

  3. 使用 on 方法订阅 userLogin 事件,并在 emit 方法中发布 userLogin 事件。

  4. 最后,使用 off 方法取消订阅 userLogin 事件。

这种模式非常适用于需要事件驱动编程的场景,例如用户界面更新、实时数据处理等。

命令模式(Command Pattern)

命令模式(Command Pattern)是一种行为设计模式,它将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。该模式在需要将操作参数化、记录、撤销或重做的场景中特别有用。

//命令模式
// 命令接口
class Command {
	execute() {
		throw new Error('execute method should be implemented')
	}
}

// 具体命令 - 开灯
class TurnOnLightCommand extends Command {
	constructor(light) {
		super()
		this.light = light
	}

	execute() {
		this.light.turnOn()
	}
}

// 具体命令 - 关灯
class TurnOffLightCommand extends Command {
	constructor(light) {
		super()
		this.light = light
	}

	execute() {
		this.light.turnOff()
	}
}

// 接收者类 - 灯
class Light {
	turnOn() {
		console.log('The light is on')
	}

	turnOff() {
		console.log('The light is off')
	}
}

// 调用者类 - 遥控器
class RemoteControl {
	constructor() {
		this.commands = []
	}

	submit(command) {
		this.commands.push(command)
		command.execute()
	}
}

// 客户端代码
const light = new Light()
const turnOnLightCommand = new TurnOnLightCommand(light)
const turnOffLightCommand = new TurnOffLightCommand(light)

const remoteControl = new RemoteControl()
remoteControl.submit(turnOnLightCommand) // 输出: The light is on
remoteControl.submit(turnOffLightCommand) // 输出: The light is off

在上面的示例中:

  1. 命令接口(Command) :定义了一个 execute 方法,这是所有命令类的接口。
  2. 具体命令类(TurnOnLightCommand 和 TurnOffLightCommand) :实现了 Command 接口,分别封装了开灯和关灯的操作。
  3. 接收者类(Light) :包含了具体的操作方法 turnOn 和 turnOff
  4. 调用者类(RemoteControl) :负责调用命令对象的 execute 方法。在这里我们使用了一个命令队列(commands)来保存所有执行过的命令。
  5. 客户端代码:创建了 Light 对象和对应的命令对象,并通过 RemoteControl 对象来提交和执行命令。

这种模式的好处是将命令的调用者和执行者解耦,使得调用者不需要知道执行命令的细节,从而实现更好的模块化和可维护性。它还提供了命令的记录、撤销和重做等功能的基础。

组合模式(Composite Pattern)

组合模式(Composite Pattern)是结构型设计模式之一,它通过将对象组合成树形结构来表示"部分-整体"的层次结构。组合模式使得客户端可以一致地处理单个对象和组合对象。以下是对组合模式的详细介绍及示例代码:

组合模式的核心思想

组合模式的核心思想是定义一个接口,表示单个对象和组合对象的共同行为,使得客户端可以一致地处理它们。通常,组合模式包含以下角色:

  1. 组件(Component) :定义了组合对象和单个对象的共同接口。
  2. 叶子节点(Leaf) :表示叶子对象,不包含任何子对象。
  3. 组合节点(Composite) :表示有子对象的组合对象,通常包含添加、删除子对象的方法。

组合模式示例

假设我们有一个公司组织结构,每个部门可以有自己的子部门或者员工。我们使用组合模式来表示这种结构。

JavaScript 代码实现

// 组件
class EmployeeComponent {
	getName() {
		throw new Error('This method must be overridden!')
	}
	getSalary() {
		throw new Error('This method must be overridden!')
	}
	print() {
		throw new Error('This method must be overridden!')
	}
}

// 叶子节点
class Employee extends EmployeeComponent {
	constructor(name, salary) {
		super()
		this.name = name
		this.salary = salary
	}
	getName() {
		return this.name
	}
	getSalary() {
		return this.salary
	}
	print() {
		console.log(`Employee: ${this.getName()}, Salary: ${this.getSalary()}`)
	}
}

// 组合节点
class Department extends EmployeeComponent {
	constructor(name) {
		super()
		this.name = name
		this.employees = []
	}
	getName() {
		return this.name
	}
	getSalary() {
		return this.employees.reduce((total, employee) => total + employee.getSalary(), 0)
	}
	add(employee) {
		this.employees.push(employee)
	}
	remove(employee) {
		this.employees = this.employees.filter(e => e !== employee)
	}
	print() {
		console.log(`Department: ${this.getName()}, Total Salary: ${this.getSalary()}`)
		this.employees.forEach(employee => employee.print())
	}
}

// 使用组合模式
const developer = new Employee('John', 5000)
const designer = new Employee('Jane', 4000)

const itDepartment = new Department('IT Department')
itDepartment.add(developer)
itDepartment.add(designer)

const hrDepartment = new Department('HR Department')
const hrManager = new Employee('Sara', 6000)
hrDepartment.add(hrManager)

const company = new Department('Company')
company.add(itDepartment)
company.add(hrDepartment)

company.print()

//### 输出结果
//Department: Company, Total Salary: 15000
//Department: IT Department, Total Salary: 9000
//Employee: John, Salary: 5000
//Employee: Jane, Salary: 4000
//Department: HR Department, Total Salary: 6000
//Employee: Sara, Salary: 6000

在这个示例中,我们定义了 EmployeeComponent 作为组件的接口, Employee 作为叶子节点, Department 作为组合节点。通过这种方式,客户端可以一致地处理单个员工和部门,并打印出组织结构的完整信息。

组合模式的优点

  • 可以清晰地定义复杂的对象树结构。
  • 客户端可以一致地处理单个对象和组合对象。
  • 易于扩展新的叶子组件和组合组件。

组合模式的缺点

  • 设计较为复杂,可能会导致类的数量增加。
  • 由于树形结构,可能会使某些操作变得复杂。

组合模式非常适合用于表示分层结构的对象,如文件系统、公司组织结构、UI 组件等。

模板方法模式(Template Method Pattern)

模板方法模式(Template Method Pattern)是一种行为型设计模式,它定义了一个操作中的算法的框架,而将一些步骤的实现延迟到子类中。通过模板方法,子类可以重新定义算法的某些步骤,而不用改变算法的结构。

模板方法模式通常包含以下几个部分:

  1. 抽象类(Abstract Class) :定义算法的骨架,并包含一个或多个抽象方法,这些抽象方法将在子类中实现。
  2. 具体子类(Concrete Class) :实现抽象类中定义的抽象方法,以完成特定的子步骤。

下面是一个使用 JavaScript 实现模板方法模式的示例:

// 抽象类
class Game {
    initialize() {
        throw new Error("This method must be overridden!");
    }
    startPlay() {
        throw new Error("This method must be overridden!");
    }
    endPlay() {
        throw new Error("This method must be overridden!");
    }

    // 模板方法
    play() {
        this.initialize();
        this.startPlay();
        this.endPlay();
    }
}

// 具体子类
class Cricket extends Game {
    initialize() {
        console.log("Cricket Game Initialized! Start playing.");
    }
    startPlay() {
        console.log("Cricket Game Started. Enjoy the game!");
    }
    endPlay() {
        console.log("Cricket Game Finished!");
    }
}

class Football extends Game {
    initialize() {
        console.log("Football Game Initialized! Start playing.");
    }
    startPlay() {
        console.log("Football Game Started. Enjoy the game!");
    }
    endPlay() {
        console.log("Football Game Finished!");
    }
}

// 使用模板方法模式
const game1 = new Cricket();
game1.play();

const game2 = new Football();
game2.play();

在这个示例中,Game 类定义了一个模板方法 play,它依次调用了 initializestartPlayendPlay 方法。这些方法在 Game 类中是抽象的(通过抛出错误来模拟抽象方法),具体的实现留给子类 CricketFootball

当调用 game1.play() 时,模板方法 play 会按照顺序调用 Cricket 类中的 initializestartPlayendPlay 方法,输出对应的消息。同理,game2.play() 会调用 Football 类中的实现。

通过这种方式,模板方法模式可以使算法的整体结构保持不变,而具体步骤的实现可以灵活变化。

享元模式(Flyweight Pattern)

享元模式(Flyweight Pattern)是一种结构型设计模式,旨在通过共享相似对象来减少内存使用和提高性能。该模式特别适合用于大量细粒度对象的场景。

享元模式的核心思想是将对象的状态分为内部状态和外部状态。内部状态是对象可共享的部分,独立于对象的环境;外部状态是对象不能共享的部分,依赖于对象的环境。

享元模式的关键点

  1. 享元接口(Flyweight) :定义了可以被共享的方法。
  2. 具体享元类(Concrete Flyweight) :实现享元接口,并定义了内部状态。
  3. 享元工厂(Flyweight Factory) :用于创建和管理享元对象,确保合理地共享对象。
  4. 客户端(Client) :维护外部状态,并使用享元对象。
// 享元接口
class Flyweight {
  operation(externalState) {
    throw new Error("This method should be overridden!");
  }
}

// 具体享元类
class ConcreteFlyweight extends Flyweight {
  constructor(sharedState) {
    super();
    this.sharedState = sharedState;
  }

  operation(externalState) {
    console.log(`Shared State: ${this.sharedState}, External State: ${externalState}`);
  }
}

// 享元工厂
class FlyweightFactory {
  constructor() {
    this.flyweights = {};
  }

  getFlyweight(sharedState) {
    if (!this.flyweights[sharedState]) {
      this.flyweights[sharedState] = new ConcreteFlyweight(sharedState);
    }
    return this.flyweights[sharedState];
  }

  getFlyweightCount() {
    return Object.keys(this.flyweights).length;
  }
}

// 客户端代码
const factory = new FlyweightFactory();

const flyweight1 = factory.getFlyweight('state1');
flyweight1.operation('external1');

const flyweight2 = factory.getFlyweight('state2');
flyweight2.operation('external2');

const flyweight3 = factory.getFlyweight('state1');
flyweight3.operation('external3');

console.log(`Number of flyweights: ${factory.getFlyweightCount()}`);

解释

  1. Flyweight 类定义了享元接口,包含 operation 方法。
  2. ConcreteFlyweight 类实现了享元接口,并包含可共享的内部状态 sharedState
  3. FlyweightFactory 类管理享元对象的创建和共享。它通过 getFlyweight 方法返回一个共享的享元对象,并通过 getFlyweightCount 方法返回当前享元对象的数量。
  4. 客户端代码通过 FlyweightFactory 获取享元对象,并通过调用享元对象的 operation 方法传递外部状态。

享元模式通过共享对象来减少内存消耗,非常适合于需要大量相似对象的场景,如图形界面中的图元(如字符、线条等)、数据缓存等。

职责链模式(Chain of Responsibility)

职责链模式(Chain of Responsibility)是一种行为设计模式,它允许你将请求沿着处理链进行发送,直到有对象处理它。处理请求的对象连接成链,并沿着链传递请求,直到某个对象能够处理它。

职责链模式的主要思想是将请求的发送者和接收者解耦。发送者不知道哪个对象会处理请求,而接收者知道如何处理请求并且可以将请求传递给下一个接收者(如果有的话)。

以下是职责链模式的一个简化示例,展示了如何在 JavaScript 中实现这一模式:

//职责链模式
class Handler {
	constructor(nextHandler) {
		this.nextHandler = nextHandler
	}

	handle(request) {
		if (this.nextHandler) {
			return this.nextHandler.handle(request)
		}
		return null
	}
}

class ConcreteHandler1 extends Handler {
	handle(request) {
		if (request === 'request1') {
			return `Handled by ConcreteHandler1`
		}
		return super.handle(request)
	}
}

class ConcreteHandler2 extends Handler {
	handle(request) {
		if (request === 'request2') {
			return `Handled by ConcreteHandler2`
		}
		return super.handle(request)
	}
}

class ConcreteHandler3 extends Handler {
	handle(request) {
		if (request === 'request3') {
			return `Handled by ConcreteHandler3`
		}
		return super.handle(request)
	}
}

// 创建职责链
const handler1 = new ConcreteHandler1(null)
const handler2 = new ConcreteHandler2(handler1)
const handler3 = new ConcreteHandler3(handler2)

// 处理请求
const request = 'request2'
const result = handler3.handle(request)

console.log(result) // 输出 "Handled by ConcreteHandler2"

注意: 在子类的静态方法中通过super调用父类的方法时,方法内部的this指向当前的子类

解释:

  1. Handler类:这是一个基础处理程序类,它包含一个指向下一个处理程序的引用。handle方法检查是否有下一个处理程序,如果有,则将请求传递给下一个处理程序。
  2. ConcreteHandler1, ConcreteHandler2, ConcreteHandler3:这些是具体的处理程序类,它们分别处理不同类型的请求。如果当前处理程序无法处理请求,它会将请求传递给下一个处理程序。
  3. 职责链的创建:职责链是通过将一个处理程序传递给下一个处理程序的构造函数来创建的。
  4. 处理请求:当一个请求到来时,它会从链的头部开始处理,并沿着链传递,直到某个处理程序能够处理它。

职责链模式的优点是请求发送者和接收者之间的解耦,以及处理程序可以灵活地组合和重用。缺点是可能导致请求处理的延迟,因为请求需要沿着链传递。

中介者模式(Mediator Pattern)

中介者模式(Mediator Pattern)是一种行为设计模式,用于减少多个对象和类之间的通信复杂性。这种模式通过一个中介者对象来处理对象之间的通信,避免了对象之间直接引用,降低了对象间的耦合度,使代码更加易于维护和扩展。

以下是中介者模式的基本结构和一个简单的JavaScript实现示例:

结构

  1. Mediator(中介者) :定义一个接口用于与各同事(Colleague)对象通信。
  2. ConcreteMediator(具体中介者) :实现中介者接口,协调各同事对象之间的交互。
  3. Colleague(同事) :每个同事类都知道它的中介者对象,并通过中介者与其他同事类交互。

JavaScript 示例

// 定义中介者接口
class Mediator {
  notify(sender, event) {
    throw new Error('This method should be overwritten!');
  }
}

// 具体中介者
class ConcreteMediator extends Mediator {
  constructor() {
    super();
    this.components = {};
  }

  addComponent(name, component) {
    this.components[name] = component;
    component.setMediator(this);
  }

  notify(sender, event) {
    if (event === 'A') {
      console.log('Mediator reacts on A and triggers following operations:');
      this.components['Component2'].doC();
    }
    if (event === 'D') {
      console.log('Mediator reacts on D and triggers following operations:');
      this.components['Component1'].doB();
      this.components['Component2'].doC();
    }
  }
}

// 同事类
class BaseComponent {
  constructor() {
    this.mediator = null;
  }

  setMediator(mediator) {
    this.mediator = mediator;
  }
}

class Component1 extends BaseComponent {
  doA() {
    console.log('Component 1 does A.');
    this.mediator.notify(this, 'A');
  }

  doB() {
    console.log('Component 1 does B.');
  }
}

class Component2 extends BaseComponent {
  doC() {
    console.log('Component 2 does C.');
  }

  doD() {
    console.log('Component 2 does D.');
    this.mediator.notify(this, 'D');
  }
}

// 使用中介者模式
const mediator = new ConcreteMediator();
const component1 = new Component1();
const component2 = new Component2();

mediator.addComponent('Component1', component1);
mediator.addComponent('Component2', component2);

console.log('Client triggers operation A.');
component1.doA();

console.log('');

console.log('Client triggers operation D.');
component2.doD();

装饰者模式(Decorator Pattern)

装饰者模式(Decorator Pattern)是一种结构型设计模式,它允许你动态地为对象添加功能,而不会改变对象的结构。装饰者模式通过创建一个装饰类来包裹原始类,从而在保持类方法签名完整的情况下提供额外的功能。

下面是一个使用 JavaScript 实现装饰者模式的示例:

// 基本功能类
class Coffee {
  cost() {
    return 5;
  }
}

// 装饰者基类
class CoffeeDecorator {
  constructor(coffee) {
    this.coffee = coffee;
  }

  cost() {
    return this.coffee.cost();
  }
}

// 添加牛奶装饰者
class MilkDecorator extends CoffeeDecorator {
  cost() {
    return this.coffee.cost() + 1;
  }
}

// 添加糖装饰者
class SugarDecorator extends CoffeeDecorator {
  cost() {
    return this.coffee.cost() + 0.5;
  }
}

// 示例使用
let myCoffee = new Coffee();
console.log(myCoffee.cost()); // 5

myCoffee = new MilkDecorator(myCoffee);
console.log(myCoffee.cost()); // 6

myCoffee = new SugarDecorator(myCoffee);
console.log(myCoffee.cost()); // 6.5

在这个示例中:

  1. Coffee 类是基本功能类,它有一个 cost 方法返回基本价格。
  2. CoffeeDecorator 是一个装饰者基类,接收一个 Coffee 对象,并委托调用 cost 方法。
  3. MilkDecorator 和 SugarDecorator 是具体的装饰者类,它们分别添加了牛奶和糖的功能,通过重写 cost 方法来增加价格。

这种设计模式非常灵活,因为它允许你在运行时动态地组合不同的装饰者,从而为对象添加功能而无需修改对象的类定义。

状态模式(State Pattern)

状态模式(State Pattern)是一种行为设计模式,它允许对象在其内部状态改变时改变其行为。状态模式将状态相关的行为分离到不同的状态类中,使得状态切换更为明确和可维护。

以下是使用 JavaScript 实现状态模式的一个简单例子:

// 定义状态接口
class State {
    handle(context) {
        throw new Error("This method must be overwritten!");
    }
}

// 定义具体状态类
class ConcreteStateA extends State {
    handle(context) {
        console.log("State A handling request and switching to State B.");
        context.setState(new ConcreteStateB());
    }
}

class ConcreteStateB extends State {
    handle(context) {
        console.log("State B handling request and switching to State A.");
        context.setState(new ConcreteStateA());
    }
}

// 定义上下文类
class Context {
    constructor(state) {
        this.setState(state);
    }

    setState(state) {
        console.log(`Switching to ${state.constructor.name}`);
        this.state = state;
    }

    request() {
        this.state.handle(this);
    }
}

// 客户端代码
const context = new Context(new ConcreteStateA());
context.request(); // State A handling request and switching to State B.
context.request(); // State B handling request and switching to State A.

在这个示例中:

  1. State 类是一个接口,定义了一个抽象方法 handle
  2. ConcreteStateA 和 ConcreteStateB 是具体的状态类,实现了 State 接口,并定义了各自的 handle 方法。在 handle 方法中,它们会根据当前状态执行操作,并将上下文的状态切换到另一个状态。
  3. Context 类持有一个状态实例,并提供 request 方法来处理请求。request 方法会调用当前状态的 handle 方法。

通过这种方式,我们可以将状态相关的行为分离到不同的类中,使得状态转换和行为更加清晰和易于维护。

适配器模式(Adapter Pattern)

适配器模式(Adapter Pattern)是一种结构型设计模式,它用于将一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的类可以一起工作。适配器模式通常用于解决现有类与新需求不兼容的问题。

适配器模式的角色

  1. 目标接口(Target Interface) :客户所期望的接口。
  2. 适配器(Adapter) :将现有接口转换为目标接口。
  3. 被适配者(Adaptee) :需要适配的类。
  4. 客户(Client) :调用目标接口的方法。

适配器模式的实现

以下是一个简单的 JavaScript 适配器模式示例:

假设我们有一个老式的播放器(OldPlayer),它只能播放 .mp3 文件,但我们需要一个新的播放器(NewPlayer),它可以播放 .mp4 文件。

代码示例

// 目标接口(新的播放器接口)
class NewPlayer {
  play(file) {
    // 默认播放方法,应该被适配器实现
    console.log("Playing file: " + file);
  }
}

// 被适配者(老式播放器)
class OldPlayer {
  playMP3(file) {
    console.log("Playing MP3 file: " + file);
  }
}

// 适配器,使 OldPlayer 适配 NewPlayer 的接口
class PlayerAdapter extends NewPlayer {
  constructor(oldPlayer) {
    super();
    this.oldPlayer = oldPlayer;
  }

  play(file) {
    if (file.endsWith('.mp3')) {
      this.oldPlayer.playMP3(file);
    } else {
      console.log("Unsupported file format: " + file);
    }
  }
}

// 客户端代码
const oldPlayer = new OldPlayer();
const adapter = new PlayerAdapter(oldPlayer);

adapter.play("song.mp3"); // Playing MP3 file: song.mp3
adapter.play("video.mp4"); // Unsupported file format: video.mp4

适配器模式的优点

  1. 提高了类的复用性:通过适配器可以使不相关的类一起工作。
  2. 灵活性和扩展性好:可以随时增加新的适配器来兼容新接口。
  3. 解耦:适配器模式将客户端和被适配者解耦,使得它们可以独立变化。

适配器模式的缺点

  1. 增加系统复杂度:引入了额外的适配器类,增加了系统的复杂度。
  2. 性能开销:可能会由于额外的转化操作带来一定的性能开销。

适用场景

  1. 旧系统与新系统集成:当需要将一个现有的类与一个新类集成时,适配器模式可以解决接口不兼容的问题。
  2. 多种接口整合:当一个系统需要整合多个接口时,可以使用适配器模式来进行统一处理。

适配器模式在现实开发中非常常见,特别是在需要维护和扩展老旧系统时,可以很方便地解决接口兼容性的问题。

单例模式(Singleton Pattern)

单例模式(Singleton Pattern)是一种常用的设计模式,确保一个类只有一个实例,并提供一个全局访问点。在 JavaScript 中实现单例模式有多种方法,其中一种是惰性单例(Lazy Singleton),即只有在第一次使用时才创建实例。

以下是实现惰性单例模式的代码示例:

const Singleton = (function() {
  let instance;

  function createInstance() {
    const object = new Object("I am the instance");
    return object;
  }

  return {
    getInstance: function() {
      if (!instance) {
        instance = createInstance();
      }
      return instance;
    }
  };
})();

// 使用示例
const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();

console.log(instance1 === instance2); // true

在这个例子中,Singleton是一个立即执行函数表达式(IIFE),它返回一个对象,该对象包含一个getInstance方法。getInstance方法会检查instance是否已经存在,如果不存在则调用createInstance方法创建实例。如果实例已经存在,则直接返回该实例。这种方式确保了单例对象在第一次被使用时才会被创建,从而实现惰性单例模式。