本文是《ES6 类聊 JavaScript 设计模式》的第三篇,介绍第三种类型的设计模式行为设计模式,其特别关注对象之间的通信。
在软件工程中, 行为型模式为设计模式的一种类型,用来识别对象之间的常用交流模式并加以实现。如此,可在进行这些交流活动时增强弹性。—— 维基百科
- 责任链模式
- 命令模式
- 迭代器模式
- 中介者模式
- 备忘录模式
13. 责任链模式
责任链模式创建了一个对象链,从一个点开始,它会停止,直到找到某个条件。
为解除请求的发送者和接收者之间耦合,而使多个对象都有机会处理这个请求。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它。—— 维基百科
实例
实例将使用一个生物攻击防御的游戏。游戏中生物将增加它的防御和攻击当它到达一定的点。它会形成一个链条,攻击和防御会增加和减少。
class Creature { constructor(name, attack, defense) { this.name = name; this.attack = attack; this.defense = defense; } toString() { return `${this.name} (攻击值:${this.attack} / 防御值:${this.defense})`; } } class CreatureModifier { constructor(creature) { this.creature = creature; this.next = null; } add(modifier) { if (this.next) { this.next.add(modifier); } else { this.next = modifier; } } handle() { if (this.next) { this.next.handle(); } } } // 增加攻击逻辑 class DoubleAttackModifier extends CreatureModifier { constructor(creature) { super(creature); } handle() { console.log(`来自 ${this.creature.name} 的双重攻击!`); this.creature.attack *= 2; super.handle(); } } // 增加防御逻辑 class IncreaseDefenseModifier extends CreatureModifier { constructor(creature) { super(creature); } handle() { if (this.creature.attack <= 2) { console.log(`增加 ${this.creature.name} 的防御!`); this.creature.defense++; } super.handle(); } }
接下来看下如何使用责任链模式定义的类。
const bacteria = new Creature("Bacteria", 1, 1); console.log(bacteria.toString()); // Bacteria (攻击值:1 / 防御值:1) const root = new CreatureModifier(bacteria); root.add(new DoubleAttackModifier(bacteria)); root.add(new IncreaseDefenseModifier(bacteria)); root.handle(); // 来自 Bacteria 的双重攻击! // 增加 Bacteria 的防御! console.log(bacteria.toString()); // Bacteria (攻击值:2 / 防御值:2)
14. 命令模式
命令模式将创建将动作封装在对象中的对象。
在面向对象的编程中,命令模式是一种行为设计模式,其中对象用于封装执行操作或稍后触发事件所需的所有信息。这些信息包括方法名称、拥有该方法的对象和方法参数的值。 —— 维基百科
实例
代码实例将举一个银行帐户的需求,假如必须存入或提取一定数量的钱,会在其中发出命令。
class BankAccount { constructor(balance = 0) { this.balance = balance; } deposit(amount) { this.balance += amount; console.log(`账户存入:${amount},余额为:${this.balance}`); } withdraw(amount) { if (this.balance - amount >= BankAccount.overdraftLimit) { this.balance -= amount; console.log(`账户提现:${amount},余额为:${this.balance}`); } } toString() { return `余额为 ${this.balance}`; } } BankAccount.overdraftLimit = -100; // 假设可以透支 100
接下来创建相应的命令:
const Action = Object.freeze({ deposit: 1, withdraw: 2, }); class BankAccountCommand { constructor(account, action, amount) { this.account = account; this.action = action; this.amount = amount; } call() { const callHandler = {}; callHandler[Action.deposit] = (amount) => this.account.deposit(amount); callHandler[Action.withdraw] = (amount) => this.account.withdraw(amount); callHandler[this.action](this.amount); } undo() { const callHandler = {}; callHandler[Action.deposit] = (amount) => this.account.withdraw(amount); callHandler[Action.withdraw] = (amount) => this.account.deposit(amount); callHandler[this.action](this.amount); } } // const bankAccount = new BankAccount(100); const cmd = new BankAccountCommand(bankAccount, Action.deposit, 50); cmd.call(); // 账户存入:50,余额为:150 console.log(bankAccount.toString()); // 余额为 150 cmd.undo(); // 账户提现:50,余额为:100 console.log(bankAccount.toString()); // 余额为 100
15. 迭代器模式
迭代器模式访问对象的元素而不暴露其底层表示。
在面向对象编程中,迭代器模式是一种设计模式,其中迭代器用于遍历容器并访问容器的元素。 —— 维基百科
实例
将举一个数组的例子,在其中打印一个数组的值,然后使用迭代器将其值对调输出。
class Stuff { constructor() { this.a = 33; this.b = 66; } [Symbol.iterator]() { let i = 0; return { next: () => { return { done: i > 1, value: this[i++ === 0 ? "a" : "b"], }; }, }; } get backwards() { let i = 0; return { next: () => { return { done: i > 1, value: this[i++ === 0 ? "b" : "a"], }; }, [Symbol.iterator]: function () { return this; }, }; } } const values = [100, 200, 300]; for (const i in values) { console.log(`索引 ${i} 的值 ${values[i]}`); } for (const v in values) { console.log(`值为 ${v}`); } const stuff = new Stuff(); console.log("=====>正常打印<======"); for (const item of stuff) { console.log(item); } console.log("=====>对调打印<======"); for (const item of stuff.backwards) { console.log(item); }
运行后在终端显示结果如下:
索引 0 的值 100 索引 1 的值 200 索引 2 的值 300 值为 0 值为 1 值为 2 =====>正常打印<====== 33 66 =====>对调打印<====== 66 33
16. 中介者模式
中介者模式添加了一个第三方对象来控制两个对象之间的交互。它允许类之间的松散耦合,因为它是唯一详细了解其方法的类。
中介者模式定义了一个对象,该对象封装了一组对象如何交互。这种模式被认为是一种行为模式,因为它可以改变程序的运行行为。在面向对象编程中,程序通常由许多类组成。—— 维基百科
实例
代码是一个人使用聊天室的例子。在这里,聊天室充当了两个人交流的中介。
class Person { constructor(name) { this.name = name; this.chatLog = []; } receive(sender, message) { const s = `${sender}:“${message}”`; console.log(`【${this.name}的聊天会话】${s}`); this.chatLog.push(s); } say(message) { this.room.broadcast(this.name, message); } pm(who, message) { this.room.message(this.name, who, message); } } // 创建聊天室 class ChatRoom { constructor() { this.people = []; } broadcast(source, message) { for (const p of this.people) { if (p.name !== source) { p.receive(source, message); } } } join(p) { const joinMsg = `${p.name} 加入聊天室`; this.broadcast("room", joinMsg); p.room = this; this.people.push(p); } message(source, destination, message) { for (const p of this.people) { if (p.name === destination) { p.receive(source, message); } } } } // 使用方式 const room = new ChatRoom(); const Quintion = new Person("Quintion"); const Devpoint = new Person("Devpoint"); room.join(Quintion); room.join(Devpoint); Quintion.say("下午好!"); const Tony = new Person("Tony"); room.join(Tony); Tony.say("大家好!");
运行后在终端可以看到如下结果:
【Quintion的聊天会话】room:“Devpoint 加入聊天室” 【Devpoint的聊天会话】Quintion:“下午好!” 【Quintion的聊天会话】room:“Tony 加入聊天室” 【Devpoint的聊天会话】room:“Tony 加入聊天室” 【Quintion的聊天会话】Tony:“大家好!” 【Devpoint的聊天会话】Tony:“大家好!”
17. 备忘录模式:Memento
备忘录模式将对象恢复到之前的状态。
备忘录模式是一种软件设计模式,它提供了将对象恢复到其先前状态的能力。备忘录模式由三个对象实现:发起者、看守者和备忘录。使用的场景是在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
实例
将以一个银行账户为例,其中存储之前的状态,并具有撤销功能。
// 备忘录 class Memento { constructor(balance) { this.balance = balance; } getBalance() { return this.balance; } } class BankAccount { constructor(balance = 0) { this.balance = balance; } deposit(amount) { this.balance += amount; return new Memento(this.balance); } restore(m) { this.balance = m.balance; } toString() { return `账户余额:${this.balance}`; } } // 这里开始如何使用上面定义的类 const bankAccount = new BankAccount(1000); const m1 = bankAccount.deposit(200); console.log(bankAccount.toString()); // 当前账户余额为:1200 const m2 = bankAccount.deposit(200); console.log(bankAccount.toString()); // 当前账户余额为:1400 // 恢复到m1 bankAccount.restore(m1); console.log(bankAccount.toString()); // 账户余额:1200 // 恢复到m2 bankAccount.restore(m2); console.log(bankAccount.toString()); // 账户余额:1400
写在最后
本文总结了行为型设计模式中的一部份,后面还将继续学习总结剩下的5种设计模式。