本文将对 20 多种 JavaScript 设计模式进行简单概述,然后结合 ES6 类的方式来编写实例代码展示其使用方式。
JavaScript 在现代前端中扮演重要的角色,相比过去能够做的事情已经不在一个级别上了。JavaScript 最大的特征是其灵活性,一般只要敢想敢写,可以把程序写得很简单,有可以写得很复杂。其灵活性导致编写 JavaScript 的时候能够不断的优化,能够不断质疑写的质量。而设计模式,是不分编程语言的,是软件工程的内容,JavaScript 强大的表现力赋予了开发者运用设计模式编写代码时创造性。
什么是设计模式?
设计模式是软件设计中常见问题的解决方案,这些模式很容易重复使用并且富有表现力。
在软件工程中,设计模式(design pattern)是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案。它并不直接用来完成代码的编写,而是描述在各种不同情况下,要怎么解决问题的一种方案。面向对象设计模式通常以类别或对象来描述其中的关系和相互作用,但不涉及用来完成应用程序的特定类别或对象。—— 维基百科
在 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
复制代码
评论