原文:
dev.to:Design Patterns in JavaScript
GitHub:Design Patterns in JavaScript
image.png

JS中的设计模式

🚀 什么是设计模式?

设计模式是软件设计领域处理通用问题的解决方案。这些模式是易于重用和具备表现力的。
根据Wikipedia:

在软件工程中,一个软件设计模式是在软件设计中在给定的上下文的情况下,一种通用的、可复用的处理普遍问题的解决方案。这不是可以直接转换成源码或者机器码的最终设计。这是对于如何解决问题,可以适用在不同的情况下的一种描述或者模板。

设计模式的分类

  • 创建型
  • 结构型
  • 行为型

    创建型设计模式

创建型设计模式用于创建对象来替换直接初始化对象。
根据Wikipedia:

在软件工程中,创建型设计模式是处理对象创建机制,试图在某种程度上创建适合当前情况的对象的一种模式。对象创建的基本形式可能导致设计的复杂度或者设计问题。创建型设计模式通过以某种方式控制对象的创建解决了这个问题。

  • 工厂方法
  • 抽象工厂
  • 构建者模式
  • 原型模式
  • 单例模式

    工厂方法(Factory Method)

它定义了创建单独对象的接口,并且可以让子类决定初始化哪个类。
根据Wikipedia:

在基于类的编程中,工厂方法模式是创建型设计模式的一种,是使用工厂方法来处理创建对象的问题,不需要指定创建对象的具体类。通过调用工厂方法来创建对象 - 或者通过指定一个接口并且在子类中实现,或者通过基类实现和被衍生类可选的重写,而不是通过调用构造器。

示例:
有一个Point的类,我们要创建 Cartesian point 和 Polar point。定义一个Point工厂来处理。

  1. CoordinateSystem = {
  2. CARTESIAN: 0,
  3. POLAR: 1,
  4. };
  5. class Point {
  6. constructor(x, y) {
  7. this.x = x;
  8. this.y = y;
  9. }
  10. static get factory() {
  11. return new PointFactory();
  12. }
  13. }

定义Point 工厂:

  1. class PointFactory {
  2. static newCartesianPoint(x, y) {
  3. return new Point(x, y);
  4. }
  5. static newPolarPoint(rho, theta) {
  6. return new Point(rho * Math.cos(theta), rho * Math.sin(theta));
  7. }
  8. }

如何使用:

  1. let point = PointFactory.newPolarPoint(5, Math.PI/2);
  2. let point2 = PointFactory.newCartesianPoint(5, 6)
  3. console.log(point);
  4. console.log(point2);

类图: [译]JS中的设计模式 - 图2

抽象工厂(Abstract Factory)

抽象工厂创建一组相同的对象而不需要指定他们的具体类。
根据Wikipedia:

抽象工厂模式提供了一种方式,封装了一组独立的工厂,这些工厂具有相同的主题,而不需要指定他们的具体类。

示例:

Drink示例,

  1. class Drink
  2. {
  3. consume() {}
  4. }
  5. class Tea extends Drink
  6. {
  7. consume() {
  8. console.log('This is Tea');
  9. }
  10. }
  11. class Coffee extends Drink
  12. {
  13. consume()
  14. {
  15. console.log(`This is Coffee`);
  16. }
  17. }

Drink工厂:

  1. class DrinkFactory
  2. {
  3. prepare(amount)
  4. }
  5. class TeaFactory extends DrinkFactory
  6. {
  7. makeTea()
  8. {
  9. console.log(`Tea Created`);
  10. return new Tea();
  11. }
  12. }
  13. class CoffeeFactory extends DrinkFactory
  14. {
  15. makeCoffee()
  16. {
  17. console.log(`Coffee Created`);
  18. return new Coffee();
  19. }
  20. }

如何使用:

  1. let teaDrinkFactory = new TeaFactory();
  2. let tea = teaDrinkFactory.makeTea()
  3. tea.consume()

类图: [译]JS中的设计模式 - 图3

构建者模式 (Builder)

构建者模式是通过简单对象构建复杂对象。
根据Wikipedia:

构建者模式是一种设计模式 ,针对面向对象编程,为多样化的对象创建问题提供了一种灵活的解决方案。

示例:
创建一个Person类,Person类存储了person的一些信息。

  1. class Person {
  2. constructor() {
  3. this.streetAddress = this.postcode = this.city = "";
  4. this.companyName = this.position = "";
  5. this.annualIncome = 0;
  6. }
  7. toString() {
  8. return (
  9. `Person lives at ${this.streetAddress}, ${this.city}, ${this.postcode}\n` +
  10. `and works at ${this.companyName} as a ${this.position} earning ${this.annualIncome}`
  11. );
  12. }
  13. }

现在创建Person``Builder

  1. class PersonBuilder {
  2. constructor(person = new Person()) {
  3. this.person = person;
  4. }
  5. get lives() {
  6. return new PersonAddressBuilder(this.person);
  7. }
  8. get works() {
  9. return new PersonJobBuilder(this.person);
  10. }
  11. build() {
  12. return this.person;
  13. }
  14. }

创建PersonJobBuilder类,并且获取Person中job信息。

  1. class PersonJobBuilder extends PersonBuilder {
  2. constructor(person) {
  3. super(person);
  4. }
  5. at(companyName) {
  6. this.person.companyName = companyName;
  7. return this;
  8. }
  9. asA(position) {
  10. this.person.position = position;
  11. return this;
  12. }
  13. earning(annualIncome) {
  14. this.person.annualIncome = annualIncome;
  15. return this;
  16. }
  17. }

PersonAddressBuilder将会保持person中address信息。

  1. class PersonAddressBuilder extends PersonBuilder {
  2. constructor(person) {
  3. super(person);
  4. }
  5. at(streetAddress) {
  6. this.person.streetAddress = streetAddress;
  7. return this;
  8. }
  9. withPostcode(postcode) {
  10. this.person.postcode = postcode;
  11. return this;
  12. }
  13. in(city) {
  14. this.person.city = city;
  15. return this;
  16. }
  17. }

使用:

  1. let personBuilder = new PersonBuilder();
  2. let person = personBuilder.lives
  3. .at("ABC Road")
  4. .in("Multan")
  5. .withPostcode("66000")
  6. .works.at("Octalogix")
  7. .asA("Engineer")
  8. .earning(10000)
  9. .build();
  10. console.log(person.toString());

[译]JS中的设计模式 - 图4

原型模式 (Prototype)

通过已有的对象创建新对象。
根据Wikipedia:

原型模式是在软件开发中一种创建型的设计模式。这被用于当对象的创建是由原型实例来决定,用于克隆生成新的对象。

示例:

Car案例:

  1. class Car {
  2. constructor(name, model) {
  3. this.name = name;
  4. this.model = model;
  5. }
  6. SetName(name) {
  7. console.log(`${name}`)
  8. }
  9. clone() {
  10. return new Car(this.name, this.model);
  11. }
  12. }

如何使用:

  1. let car = new Car();
  2. car.SetName('Audi);
  3. let car2 = car.clone()
  4. car2.SetName('BMW')

单例模式 (Singleton)

单例模式确保了对于一个特定的类只会有一个对象。
根据Wikipedia:

在软件工程中,单例模式是一种设计模式用来限制一个类的初始化只会有一个 ‘single ‘ 实例。当需要在跨系统中协调一些动作只需要一个精确的对象时这是有用的。

示例:
创建一个单例类。

  1. class Singleton {
  2. constructor()
  3. {
  4. const instance = this.constructor.instance;
  5. if (instance) {
  6. return instance;
  7. }
  8. this.constructor.instance = this;
  9. }
  10. say() {
  11. console.log('Saying...')
  12. }
  13. }

如何使用:

  1. let s1 = new Singleton();
  2. let s2 = new Singleton();
  3. console.log('Are they same? ' + (s1 === s2));
  4. s1.say();

结构性设计模式

结构型设计模式是关于类和对象组合的。用继承来组合接口。
根据Wikipedia:

在软件工程中,结构型设计模式是用于减轻设计,通过识别出一种简单的方式,来表达实体之间关系的设计模式。

分类:

  • 适配器模式
  • 桥接模式
  • 组合模式
  • 装饰器模式
  • 外观模式
  • 轻量模式
  • 代理模式

适配器模式(Adapter)

适配器模式允许一些拥有不兼容接口的类一起工作,通过已有的类包裹自己的接口。
根据Wikipedia:

在软件工程中,适配器模式是一种软件设计模式用于允许 已有类的接口可以被当做另一种接口使用。这经常被用于已有的类能够与其他的类一起工作而不需要修改源代码。

示例:
calculator的示例。Calculator1是老接口,Calculator2是新接口。创建一个适配器,用于包裹新的接口,并且用新的方法返回结果:

  1. class Calculator1 {
  2. constructor() {
  3. this.operations = function(value1, value2, operation) {
  4. switch (operation) {
  5. case 'add':
  6. return value1 + value2;
  7. case 'sub':
  8. return value1 - value2;
  9. }
  10. };
  11. }
  12. }
  13. class Calculator2 {
  14. constructor() {
  15. this.add = function(value1, value2) {
  16. return value1 + value2;
  17. };
  18. this.sub = function(value1, value2) {
  19. return value1 - value2;
  20. };
  21. }
  22. }

创建Adapter类:

  1. class CalcAdapter {
  2. constructor() {
  3. const cal2 = new Calculator2();
  4. this.operations = function(value1, value2, operation) {
  5. switch (operation) {
  6. case 'add':
  7. return cal2.add(value1, value2);
  8. case 'sub':
  9. return cal2.sub(value1, value2);
  10. }
  11. };
  12. }
  13. }

如何使用:

  1. const adaptedCalc = new CalcAdapter();
  2. console.log(adaptedCalc.operations(10, 55, 'sub'));

[译]JS中的设计模式 - 图5

桥接模式(Bridge)

桥接模式用于分离抽象和实现,以便于两者能独立变化。
根据Wikipedia:

桥接模式是一种结构性设计模式,可以让你把一个庞大的类或者一组紧密相关的类分离成两组单独的分类,抽象和实现,这样他们就能彼此单独开发。

示例:
创建一个Renderer类来渲染多种图形:

  1. class VectorRenderer {
  2. renderCircle(radius) {
  3. console.log(`Drawing a circle of radius ${radius}`);
  4. }
  5. }
  6. class RasterRenderer {
  7. renderCircle(radius) {
  8. console.log(`Drawing pixels for circle of radius ${radius}`);
  9. }
  10. }
  11. class Shape {
  12. constructor(renderer) {
  13. this.renderer = renderer;
  14. }
  15. }
  16. class Circle extends Shape {
  17. constructor(renderer, radius) {
  18. super(renderer);
  19. this.radius = radius;
  20. }
  21. draw() {
  22. this.renderer.renderCircle(this.radius);
  23. }
  24. resize(factor) {
  25. this.radius *= factor;
  26. }
  27. }

如何使用:

  1. let raster = new RasterRenderer();
  2. let vector = new VectorRenderer();
  3. let circle = new Circle(vector, 5);
  4. circle.draw();
  5. circle.resize(2);
  6. circle.draw();

类图: [译]JS中的设计模式 - 图6

组合模式(Composite)

组合多个对象以便于他们能作为一个单独的对象来使用。
根据Wikipedia:

组合设计模式描述了一组对象被同样的方式对待,作为同种类型对象的一个实例。

示例:
job案例:

  1. class Employer{
  2. constructor(name, role){
  3. this.name=name;
  4. this.role=role;
  5. }
  6. print(){
  7. console.log("name:" +this.name + " relaxTime: " );
  8. }
  9. }

创建GroupEmployer

  1. class EmployerGroup{
  2. constructor(name, composite=[]){
  3. console.log(name)
  4. this.name=name;
  5. this.composites=composite;
  6. }
  7. print(){
  8. console.log(this.name);
  9. this.composites.forEach(emp=>{
  10. emp.print();
  11. })
  12. }
  13. }

如何使用:

  1. let zee= new Employer("zee","developer")
  2. let shan= new Employer("shan","developer")
  3. let groupDevelopers = new EmployerGroup( "Developers", [zee,shan] );

[译]JS中的设计模式 - 图7

装饰器模式(Decorator)

动态的增加或者覆盖对象的行为。
根据Wikipedia:

装饰器模式是一种设计模式允许给单个对象添加行为,动态的,而不会影响来自于同一个类中的其他对象。

示例:
下面有一个Shape类和Circle类,如果要画一个圆,那么就创建一个画圆的方法即可。如果想画一个红色的圆?这时候装饰器模式就可以帮助我们了。

  1. class Shape {
  2. constructor(color) {
  3. this.color = color;
  4. }
  5. }
  6. class Circle extends Shape {
  7. constructor(radius = 0) {
  8. super();
  9. this.radius = radius;
  10. }
  11. resize(factor) {
  12. this.radius *= factor;
  13. }
  14. toString() {
  15. return `A circle ${this.radius}`;
  16. }
  17. }

创建一个ColoredShape类:

  1. class ColoredShape extends Shape {
  2. constructor(shape, color) {
  3. super();
  4. this.shape = shape;
  5. this.color = color;
  6. }
  7. toString() {
  8. return `${this.shape.toString()}` + `has the color ${this.color}`;
  9. }
  10. }

如何使用:

  1. let circle = new Circle(2);
  2. console.log(circle);
  3. let redCircle = new ColoredShape(circle, "red");
  4. console.log(redCircle.toString());

[译]JS中的设计模式 - 图8

外观模式(Facade)

外观模式为复杂代码提供了一种简单接口。
根据Wikipedia:

外观模式是一种经常应用在面向对象编程中的软件设计模式。和建筑的外观类似,一个facade是一个对象作为前置接口,用来隐藏更复杂的潜在的或者结构化的代码。

示例:
下面看一个客户端与计算机交互的例子:

  1. class CPU {
  2. freeze() {console.log("Freezed....")}
  3. jump(position) { console.log("Go....")}
  4. execute() { console.log("Run....") }
  5. }
  6. class Memory {
  7. load(position, data) { console.log("Load....") }
  8. }
  9. class HardDrive {
  10. read(lba, size) { console.log("Read....") }
  11. }

创建Facade

  1. class ComputerFacade {
  2. constructor() {
  3. this.processor = new CPU();
  4. this.ram = new Memory();
  5. this.hd = new HardDrive();
  6. }
  7. start() {
  8. this.processor.freeze();
  9. this.ram.load(this.BOOT_ADDRESS, this.hd.read(this.BOOT_SECTOR, this.SECTOR_SIZE));
  10. this.processor.jump(this.BOOT_ADDRESS);
  11. this.processor.execute();
  12. }
  13. }

如何使用:

  1. let computer = new ComputerFacade();
  2. computer.start();

[译]JS中的设计模式 - 图9

轻量模式(Flyweight)

轻量模式用于减轻创建同样对象的内存消耗。
根据Wikipedia:

轻量模式是一个对象用于减少内存的使用,通过与类似的对象尽可能多的共享数据。

示例:
看一个user的案例。假设有多个user拥有同一name。我们可以通过存储name来节约内存和给所有有同样们名字的user一个引用。

  1. class User
  2. {
  3. constructor(fullName)
  4. {
  5. this.fullName = fullName;
  6. }
  7. }
  8. class User2
  9. {
  10. constructor(fullName)
  11. {
  12. let getOrAdd = function(s)
  13. {
  14. let idx = User2.strings.indexOf(s);
  15. if (idx !== -1) return idx;
  16. else
  17. {
  18. User2.strings.push(s);
  19. return User2.strings.length - 1;
  20. }
  21. };
  22. this.names = fullName.split(' ').map(getOrAdd);
  23. }
  24. }
  25. User2.strings = [];
  26. function getRandomInt(max) {
  27. return Math.floor(Math.random() * Math.floor(max));
  28. }
  29. let randomString = function()
  30. {
  31. let result = [];
  32. for (let x = 0; x < 10; ++x)
  33. result.push(String.fromCharCode(65 + getRandomInt(26)));
  34. return result.join('');
  35. };

接下来通过创建10k对象,对比下使用轻量和不使用轻量的内存消耗。

  1. let users = [];
  2. let users2 = [];
  3. let firstNames = [];
  4. let lastNames = [];
  5. for (let i = 0; i < 100; ++i)
  6. {
  7. firstNames.push(randomString());
  8. lastNames.push(randomString());
  9. }
  10. // making 10k users
  11. for (let first of firstNames)
  12. for (let last of lastNames) {
  13. users.push(new User(`${first} ${last}`));
  14. users2.push(new User2(`${first} ${last}`));
  15. }
  16. console.log(`10k users take up approx ` +
  17. `${JSON.stringify(users).length} chars`);
  18. let users2length =
  19. [users2, User2.strings].map(x => JSON.stringify(x).length)
  20. .reduce((x,y) => x+y);
  21. console.log(`10k flyweight users take up approx ` +
  22. `${users2length} chars`);

代理模式(Proxy)

通过使用代理,一个类可以体现另一个类的功能。
根据Wikipedia:

代理模式是一种软件设计模式。一个代理,他最通用的形式,是一个类,对其他的东西起一个接口的作用。

示例:
看一个value代理的例子。

  1. class Percentage {
  2. constructor(percent) {
  3. this.percent = percent;
  4. }
  5. toString() {
  6. return `${this.percent}&`;
  7. }
  8. valueOf() {
  9. return this.percent / 100;
  10. }
  11. }

如何使用:

  1. let fivePercent = new Percentage(5);
  2. console.log(fivePercent.toString());
  3. console.log(`5% of 50 is ${50 * fivePercent}`);

行为型设计模式

行为型模式是专门关于或者涉及对象之间通信的模式。
根据Wikipedia:

在软件工程中,行为型设计模式是用于对象间识别通用通信模式的设计模式。通过应用这些模式,在执行对象间通信时可以提升灵活性。

  • 责任链模式
  • 命令模式
  • 迭代器模式
  • 中介者模式
  • 备忘录模式
  • 观察者
  • 访问者
  • 策略模式
  • 状态模式
  • 模板方法

责任链模式(Chain of Responsibility)

责任链创建对象链。从一个点开始,直到它发现一个特定的条件后停止。
根据Wikipedia:

在面向对象设计,责任链模式包含了命令对象的来源和一系列的处理运算对象。

示例:

我们创建一个带有creature的游戏的例子。creature将会提升他的防护性,当他到达一个特定的点的时候将会攻击。创建一个链,或者攻击或者防护。

  1. class Creature {
  2. constructor(name, attack, defense) {
  3. this.name = name;
  4. this.attack = attack;
  5. this.defense = defense;
  6. }
  7. toString() {
  8. return `${this.name} (${this.attack}/${this.defense})`;
  9. }
  10. }
  11. class CreatureModifier {
  12. constructor(creature) {
  13. this.creature = creature;
  14. this.next = null;
  15. }
  16. add(modifier) {
  17. if (this.next) this.next.add(modifier);
  18. else this.next = modifier;
  19. }
  20. handle() {
  21. if (this.next) this.next.handle();
  22. }
  23. }
  24. class NoBonusesModifier extends CreatureModifier {
  25. constructor(creature) {
  26. super(creature);
  27. }
  28. handle() {
  29. console.log("No bonuses for you!");
  30. }
  31. }

加强攻击:

  1. class DoubleAttackModifier extends CreatureModifier {
  2. constructor(creature) {
  3. super(creature);
  4. }
  5. handle() {
  6. console.log(`Doubling ${this.creature.name}'s attack`);
  7. this.creature.attack *= 2;
  8. super.handle();
  9. }
  10. }

加强防护:

  1. class IncreaseDefenseModifier extends CreatureModifier {
  2. constructor(creature) {
  3. super(creature);
  4. }
  5. handle() {
  6. if (this.creature.attack <= 2) {
  7. console.log(`Increasing ${this.creature.name}'s defense`);
  8. this.creature.defense++;
  9. }
  10. super.handle();
  11. }
  12. }

使用:

  1. let peekachu = new Creature("Peekachu", 1, 1);
  2. console.log(peekachu.toString());
  3. let root = new CreatureModifier(peekachu);
  4. root.add(new DoubleAttackModifier(peekachu));
  5. root.add(new IncreaseDefenseModifier(peekachu));
  6. root.handle();
  7. console.log(peekachu.toString());

命令模式(Command)

创建对象并且封装对象中行为的模式。
根据Wikipedia:

在面向对象编程中,命令模式是一种行为设计模式,一个对象封装了所有的信息,并在稍后需要执行一个动作或者触发一个事件。这些信息包括方法名称,以及拥有方法和方法参数值的对象。

示例:
举一个银行账户的例子,当我们要存取一定数量的钱时,就发出一个命令。

  1. class BankAccount {
  2. constructor(balance = 0) {
  3. this.balance = balance;
  4. }
  5. deposit(amount) {
  6. this.balance += amount;
  7. console.log(`Deposited ${amount} Total balance ${this.balance}`);
  8. }
  9. withdraw(amount) {
  10. if (this.balance - amount >= BankAccount.overdraftLimit) {
  11. this.balance -= amount;
  12. console.log("Widhdrawn");
  13. }
  14. }
  15. toString() {
  16. return `Balance ${this.balance}`;
  17. }
  18. }
  19. BankAccount.overdraftLimit = -500;
  20. let Action = Object.freeze({
  21. deposit: 1,
  22. withdraw: 2,
  23. });

创建命令:

  1. class BankAccountCommand {
  2. constructor(account, action, amount) {
  3. this.account = account;
  4. this.action = action;
  5. this.amount = amount;
  6. }
  7. call() {
  8. switch (this.action) {
  9. case Action.deposit:
  10. this.account.deposit(this.amount);
  11. break;
  12. case Action.withdraw:
  13. this.account.withdraw(this.amount);
  14. break;
  15. }
  16. }
  17. undo() {
  18. switch (this.action) {
  19. case Action.deposit:
  20. this.account.withdraw(this.amount);
  21. break;
  22. case Action.withdraw:
  23. this.account.deposit(this.amount);
  24. break;
  25. }
  26. }
  27. }

如何使用:

  1. let bankAccount = new BankAccount(100);
  2. let cmd = new BankAccountCommand(bankAccount, Action.deposit, 50);
  3. cmd.call();
  4. console.log(bankAccount.toString());
  5. cmd.undo();
  6. console.log(bankAccount.toString());

迭代器(Iterator)

迭代器访问对象中的元素而不需要暴露潜藏在下面的表现。
根据Wikipedia:

在面向对象编程中,迭代器模式是一种设计模式指的是一个迭代器用于遍历一个容器,并且访问容器中的元素。

示例:
举一个打印数组中打印值的例子,并用迭代器倒序打印。

  1. class Stuff
  2. {
  3. constructor()
  4. {
  5. this.a = 11;
  6. this.b = 22;
  7. }
  8. [Symbol.iterator]()
  9. {
  10. let i = 0;
  11. let self = this;
  12. return {
  13. next: function()
  14. {
  15. return {
  16. done: i > 1,
  17. value: self[i++ === 0 ? 'a' : 'b']
  18. };
  19. }
  20. }
  21. }
  22. get backwards()
  23. {
  24. let i = 0;
  25. let self = this;
  26. return {
  27. next: function()
  28. {
  29. return {
  30. done: i > 1,
  31. value: self[i++ === 0 ? 'b' : 'a']
  32. };
  33. },
  34. // make iterator iterable
  35. [Symbol.iterator]: function() { return this; }
  36. }
  37. }
  38. }

如何使用:

  1. let values = [100, 200, 300];
  2. for (let i in values)
  3. {
  4. console.log(`Element at pos ${i} is ${values[i]}`);
  5. }
  6. for (let v of values)
  7. {
  8. console.log(`Value is ${v}`);
  9. }
  10. let stuff = new Stuff();
  11. for (let item of stuff)
  12. console.log(`${item}`);
  13. for (let item of stuff.backwards)
  14. console.log(`${item}`);

中介者模式(Mediator)

中介者模式是添加第三方对象用来控制两个对象的交互。它允许类之间的松散耦合,并成为拥有方法细节的唯一的类。
根据Wikipedia:

中介者模式定义了一个对象,这个对象封装了一组对象是如何交互的。这个模式被认为是行为模式是由于它可以改变程序运行行为的方式。在面向对象中,程序通常有很多的类组成。

示例:
举一个person使用chat room的例子。这里的chatroom扮演了中介者的角色,用于两个人之间的通信。

  1. class Person {
  2. constructor(name) {
  3. this.name = name;
  4. this.chatLog = [];
  5. }
  6. receive(sender, message) {
  7. let s = `${sender}: '${message}'`;
  8. console.log(`[${this.name}'s chat session] ${s}`);
  9. this.chatLog.push(s);
  10. }
  11. say(message) {
  12. this.room.broadcast(this.name, message);
  13. }
  14. pm(who, message) {
  15. this.room.message(this.name, who, message);
  16. }
  17. }

创建ChatRoom:

  1. class ChatRoom {
  2. constructor() {
  3. this.people = [];
  4. }
  5. broadcast(source, message) {
  6. for (let p of this.people)
  7. if (p.name !== source) p.receive(source, message);
  8. }
  9. join(p) {
  10. let joinMsg = `${p.name} joins the chat`;
  11. this.broadcast("room", joinMsg);
  12. p.room = this;
  13. this.people.push(p);
  14. }
  15. message(source, destination, message) {
  16. for (let p of this.people)
  17. if (p.name === destination) p.receive(source, message);
  18. }
  19. }

如何使用:

  1. let room = new ChatRoom();
  2. let zee = new Person("Zee");
  3. let shan = new Person("Shan");
  4. room.join(zee);
  5. room.join(shan);
  6. zee.say("Hello!!");
  7. let doe = new Person("Doe");
  8. room.join(doe);
  9. doe.say("Hello everyone!");

备忘录模式(Memento)

备忘录模式是恢复一个对象之前状态。
根据Wikipedia:

备忘录模式是一种软件设计模式提供了一种恢复一个对象之前状态的能力。备忘录模式有三个对象实现:发起者,守护者和记录者

示例:
举一个银行账户的例子,我们保存之前的状态,和撤回的功能。

  1. class Memento {
  2. constructor(balance) {
  3. this.balance = balance;
  4. }
  5. }

添加银行账户:

  1. class BankAccount {
  2. constructor(balance = 0) {
  3. this.balance = balance;
  4. }
  5. deposit(amount) {
  6. this.balance += amount;
  7. return new Memento(this.balance);
  8. }
  9. restore(m) {
  10. this.balance = m.balance;
  11. }
  12. toString() {
  13. return `Balance: ${this.balance}`;
  14. }
  15. }

如何使用:

  1. let bankAccount = new BankAccount(100);
  2. let m1 = bankAccount.deposit(50);
  3. console.log(bankAccount.toString());
  4. // restore to m1
  5. bankAccount.restore(m1);
  6. console.log(bankAccount.toString());

观察者模式(Observer)

观察者模式允许一些观察对象看到一个事件。
根据Wikipedia:

观察者模式是一种软件设计模式,有一个主题对象,一个具体对象,维护了一组依赖清单,叫做观察者,自动的提醒状态的变化,通常是调用它们其中的一个方法。

示例:
举一个person的例子,当person生病了,会显示一个提醒。

  1. class Event {
  2. constructor() {
  3. this.handlers = new Map();
  4. this.count = 0;
  5. }
  6. subscribe(handler) {
  7. this.handlers.set(++this.count, handler);
  8. return this.count;
  9. }
  10. unsubscribe(idx) {
  11. this.handlers.delete(idx);
  12. }
  13. fire(sender, args) {
  14. this.handlers.forEach((v, k) => v(sender, args));
  15. }
  16. }
  17. class FallsIllArgs {
  18. constructor(address) {
  19. this.address = address;
  20. }
  21. }
  22. class Person {
  23. constructor(address) {
  24. this.address = address;
  25. this.fallsIll = new Event();
  26. }
  27. catchCold() {
  28. this.fallsIll.fire(this, new FallsIllArgs(this.address));
  29. }
  30. }

如何使用:

  1. let person = new Person("ABC road");
  2. let sub = person.fallsIll.subscribe((s, a) => {
  3. console.log(`A doctor has been called ` + `to ${a.address}`);
  4. });
  5. person.catchCold();
  6. person.catchCold();
  7. person.fallsIll.unsubscribe(sub);
  8. person.catchCold();

访问者模式(Visitor)

访问者模式是给对象添加操作而不需要修改这些对象。
根据Wikipedia:

访问者设计模式是一种将算法从操作的对象结构中分离出来的方法。这种分离一个实用的结果就是给已有的对象结构添加新的操作而不需要修改结构的能力。

示例:
举一个 NumberExpression的例子。

  1. class NumberExpression
  2. {
  3. constructor(value)
  4. {
  5. this.value = value;
  6. }
  7. print(buffer)
  8. {
  9. buffer.push(this.value.toString());
  10. }
  11. }

创建AdditionExpression

  1. class AdditionExpression
  2. {
  3. constructor(left, right)
  4. {
  5. this.left = left;
  6. this.right = right;
  7. }
  8. print(buffer)
  9. {
  10. buffer.push('(');
  11. this.left.print(buffer);
  12. buffer.push('+');
  13. this.right.print(buffer);
  14. buffer.push(')');
  15. }
  16. }

如何使用:

  1. // 5 + (1+9)
  2. let e = new AdditionExpression(
  3. new NumberExpression(5),
  4. new AdditionExpression(
  5. new NumberExpression(1),
  6. new NumberExpression(9)
  7. )
  8. );
  9. let buffer = [];
  10. e.print(buffer);
  11. console.log(buffer.join(''));

策略模式(Strategy)

策略模式允许在特定的情况下选择其中的一种算法。
根据Wikipedia:

策略模式是一种软件行为设计模式允许在运行时选择一种设计模式。除了直接实现单一的一个设计模式,代码接收到运行时的指令来决定一组算法里面选择哪一个来使用。

示例:
举个例子,关于文字处理器,基于哪种策略(HTML or Markdown)来显示数据。

  1. let OutputFormat = Object.freeze({
  2. markdown: 0,
  3. html: 1,
  4. });
  5. class ListStrategy {
  6. start(buffer) {}
  7. end(buffer) {}
  8. addListItem(buffer, item) {}
  9. }
  10. class MarkdownListStrategy extends ListStrategy {
  11. addListItem(buffer, item) {
  12. buffer.push(` * ${item}`);
  13. }
  14. }
  15. class HtmlListStrategy extends ListStrategy {
  16. start(buffer) {
  17. buffer.push("<ul>");
  18. }
  19. end(buffer) {
  20. buffer.push("</ul>");
  21. }
  22. addListItem(buffer, item) {
  23. buffer.push(` <li>${item}</li>`);
  24. }
  25. }

创建TextProcessor

  1. class TextProcessor {
  2. constructor(outputFormat) {
  3. this.buffer = [];
  4. this.setOutputFormat(outputFormat);
  5. }
  6. setOutputFormat(format) {
  7. switch (format) {
  8. case OutputFormat.markdown:
  9. this.listStrategy = new MarkdownListStrategy();
  10. break;
  11. case OutputFormat.html:
  12. this.listStrategy = new HtmlListStrategy();
  13. break;
  14. }
  15. }
  16. appendList(items) {
  17. this.listStrategy.start(this.buffer);
  18. for (let item of items) this.listStrategy.addListItem(this.buffer, item);
  19. this.listStrategy.end(this.buffer);
  20. }
  21. clear() {
  22. this.buffer = [];
  23. }
  24. toString() {
  25. return this.buffer.join("\n");
  26. }
  27. }

如何使用:

  1. let tp = new TextProcessor();
  2. tp.setOutputFormat(OutputFormat.markdown);
  3. tp.appendList(["one", "two", "three"]);
  4. console.log(tp.toString());
  5. tp.clear();
  6. tp.setOutputFormat(OutputFormat.html);
  7. tp.appendList(["one", "two", "three"]);
  8. console.log(tp.toString());

状态模式(State)

状态模式是当对象内部状态改变了就修改对象的行为。
根据Wikipedia:

状态模式是一种行为型软件设计模式允许当对象的内部状态改变时修改它的行为。这种模式接近于有限状态机的概念。

示例:
举一个电灯开关的例子,当开关电灯时,它的状态变化。

  1. class Switch {
  2. constructor() {
  3. this.state = new OffState();
  4. }
  5. on() {
  6. this.state.on(this);
  7. }
  8. off() {
  9. this.state.off(this);
  10. }
  11. }
  12. class State {
  13. constructor() {
  14. if (this.constructor === State) throw new Error("abstract!");
  15. }
  16. on(sw) {
  17. console.log("Light is already on.");
  18. }
  19. off(sw) {
  20. console.log("Light is already off.");
  21. }
  22. }

创建状态类:

  1. class OnState extends State {
  2. constructor() {
  3. super();
  4. console.log("Light turned on.");
  5. }
  6. off(sw) {
  7. console.log("Turning light off...");
  8. sw.state = new OffState();
  9. }
  10. }
  11. class OffState extends State {
  12. constructor() {
  13. super();
  14. console.log("Light turned off.");
  15. }
  16. on(sw) {
  17. console.log("Turning light on...");
  18. sw.state = new OnState();
  19. }
  20. }

如何使用:

  1. let switch = new Switch();
  2. switch.on();
  3. switch.off();

模板方法模式(Template Method)

模板方法模式将算法的框架定义为一个抽象类,以及他应该是如何执行的。
根据Wikipedia:

模板方法是在超类中的一个方法,通常是一个抽象超类,按照一些高级的步骤定义了一个操作的框架。

示例:
举一个chess游戏的例子:

  1. class Game {
  2. constructor(numberOfPlayers) {
  3. this.numberOfPlayers = numberOfPlayers;
  4. this.currentPlayer = 0;
  5. }
  6. run() {
  7. this.start();
  8. while (!this.haveWinner) {
  9. this.takeTurn();
  10. }
  11. console.log(`Player ${this.winningPlayer} wins.`);
  12. }
  13. start() {}
  14. get haveWinner() {}
  15. takeTurn() {}
  16. get winningPlayer() {}
  17. }

创建chess类:

  1. class Chess extends Game {
  2. constructor() {
  3. super(2);
  4. this.maxTurns = 10;
  5. this.turn = 1;
  6. }
  7. start() {
  8. console.log(
  9. `Starting a game of chess with ${this.numberOfPlayers} players.`
  10. );
  11. }
  12. get haveWinner() {
  13. return this.turn === this.maxTurns;
  14. }
  15. takeTurn() {
  16. console.log(`Turn ${this.turn++} taken by player ${this.currentPlayer}.`);
  17. this.currentPlayer = (this.currentPlayer + 1) % this.numberOfPlayers;
  18. }
  19. get winningPlayer() {
  20. return this.currentPlayer;
  21. }
  22. }

如何使用:

  1. let chess = new Chess();
  2. chess.run();

image.png