设计模式-TS版本(类似java)
发表于:2024-06-03 |

前言

设计模式,这个听起来高大上的的东西,其实也没有那么晦涩难懂,只要愿意学习,还是比较好理解的。可能大家平时写JS中感觉用处不大,这个其实和算法一样,你只是潜移默化的用到了这个东西而不自知。ok,废话不多说,接下来我和大家一点点来学习一下设计模式。

注意事项

有些设计模式在前端并不涉及,我会使用一些java的代码作为替代,相信大家应该也能看得懂。

设计模式简介

设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理地运用设计模式可以完美地解决很多问题,每种模式在现实中都有相应的原理来与之对应,每种模式都描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是设计模式能被广泛应用的原因。

设计模式中面向对象设计原则

  • 对接口编程而不是对实现编程。
  • 优先使用对象组合而不是继承。

设计模式的类型

根据设计模式的参考书 Design Patterns - Elements of Reusable Object-Oriented Software(中文译名:设计模式 - 可复用的面向对象软件元素) 中所提到的,总共有 23 种设计模式。这些模式可以分为三大类:创建型模式(Creational Patterns)、结构型模式(Structural Patterns)、行为型模式(Behavioral Patterns)。

创建型模式

这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。

  • 工厂模式(Factory Pattern)
  • 抽象工厂模式(Abstract Factory Pattern)
  • 单例模式(Singleton Pattern)
  • 建造者模式(Builder Pattern)
  • 原型模式(Prototype Pattern)

结构式模式

这些模式关注对象之间的组合和关系,旨在解决如何构建灵活且可复用的类和对象结构。

  • 适配器模式(Adapter Pattern)
  • 桥接模式(Bridge Pattern)
  • 过滤器模式(Filter、Criteria Pattern)
  • 组合模式(Composite Pattern)
  • 装饰器模式(Decorator Pattern)
  • 外观模式(Facade Pattern)
  • 享元模式(Flyweight Pattern)
  • 代理模式(Proxy Pattern)

行为模式

这些模式关注对象之间的通信和交互,旨在解决对象之间的责任分配和算法的封装。

  • 责任链模式(Chain of Responsibility Pattern)
  • 命令模式(Command Pattern)
  • 解释器模式(Interpreter Pattern)
  • 迭代器模式(Iterator Pattern)
  • 中介者模式(Mediator Pattern)
  • 备忘录模式(Memento Pattern)
  • 观察者模式(Observer Pattern)
  • 状态模式(State Pattern)
  • 空对象模式(Null Object Pattern)
  • 策略模式(Strategy Pattern)
  • 模板模式(Template Pattern)
  • 访问者模式(Visitor Pattern)

设计模式的关系

这里给大家贴一张图,大家大概看一下设计模式的关系
设计模式关系

创建型模式

ok,接下来我们开始正式讲解设计模式模块,先来说一下创建型模式的模块

工厂模式

概念

它提供了一种创建对象的方式,使得创建对象的过程与使用对象的过程分离。

工厂模式提供了一种创建对象的方式,而无需指定要创建的具体类。

通过使用工厂模式,可以将对象的创建逻辑封装在一个工厂类中,而不是在客户端代码中直接实例化对象,这样可以提高代码的可维护性和可扩展性。

意图

定义一个创建对象的接口,让其子类决定实例化哪一个具体的类。工厂模式使对象的创建过程延迟到子类。

何时使用

当我们需要在不同条件下创建不同实例时。注意:工厂模式适用于生成复杂对象的场景。如果对象较为简单,通过 new 即可完成创建,则不必使用工厂模式。使用工厂模式会引入一个工厂类,增加系统复杂度。

如何解决

通过让子类实现工厂接口,返回一个抽象的产品。

关键

对象的创建过程在子类中实现

优点与缺点

优点:

1.调用者只需要知道对象的名称即可创建对象。
2.扩展性高,如果需要增加新产品,只需扩展一个工厂类即可。
3.屏蔽了产品的具体实现,调用者只关心产品的接口。

缺点:

每次增加一个产品时,都需要增加一个具体类和对应的工厂,使系统中类的数量成倍增加,增加了系统的复杂度和具体类的依赖

结构

1.抽象产品(Abstract Product):定义了产品的共同接口或抽象类。它可以是具体产品类的父类或接口,规定了产品对象的共同方法。
2.具体产品(Concrete Product):实现了抽象产品接口,定义了具体产品的特定行为和属性。
3.抽象工厂(Abstract Factory):声明了创建产品的抽象方法,可以是接口或抽象类。它可以有多个方法用于创建不同类型的产品。
4.具体工厂(Concrete Factory):实现了抽象工厂接口,负责实际创建具体产品的对象。

代码举例

如果大家要测试的话,这个代码写ts里面哈,js不太认interface这个接口字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 1. 创建一个工厂类
class Factory {
create(name: string) {
if (name === "product1") {
return new Product1();
} else if (name === "product2") {
return new Product2();
}
}
}
// 2. 创建一个产品接口
interface Product {
operation(): string;
}
// 3. 创建两个具体产品类
class Product1 implements Product {
operation() {
return "product1";
}
}
class Product2 implements Product {
operation() {
return "product2";
}
}
// 4. 使用工厂类创建具体产品
const factory = new Factory();
const product1 = factory.create("product1");
const product2 = factory.create("product2");
// 5. 调用产品的方法
console.log(product1.operation()); // product1
console.log(product2.operation()); // product2

抽象工厂模式

概念

抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。

抽象工厂模式提供了一种创建一系列相关或相互依赖对象的接口,而无需指定具体实现类。通过使用抽象工厂模式,可以将客户端与具体产品的创建过程解耦,使得客户端可以通过工厂接口来创建一族产品。

意图

提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们的具体类。

何时使用

当系统需要创建多个相关或依赖的对象,而不需要指定具体类时。

如何解决

在一个产品族中定义多个产品,由具体工厂实现创建这些产品的方法。

关键

在一个工厂中聚合多个同类产品的创建方法。

优点与缺点

优点

1.确保同一产品族的对象一起工作。
2.客户端不需要知道每个对象的具体类,简化了代码。

缺点

扩展产品族非常困难。增加一个新的产品族需要修改抽象工厂和所有具体工厂的代码。

结构

抽象工厂模式包含以下几个主要角色:
1.抽象工厂(Abstract Factory):声明了一组用于创建产品对象的方法,每个方法对应一种产品类型。抽象工厂可以是接口或抽象类。
2.具体工厂(Concrete Factory):实现了抽象工厂接口,负责创建具体产品对象的实例。
3.抽象产品(Abstract Product):定义了一组产品对象的共同接口或抽象类,描述了产品对象的公共方法。
4.具体产品(Concrete Product):实现了抽象产品接口,定义了具体产品的特定行为和属性。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
// 1. 创建一个接口
interface Factory {
createProductA(): ProductA;
createProductB(): ProductB;
}

// 2. 创建一个接口的实现类
class Factory1 implements Factory {
createProductA(): ProductA {
return new ProductA1();
}
createProductB(): ProductB {
return new ProductB1();
}
}

// 3. 创建一个接口的实现类
class Factory2 implements Factory {
createProductA(): ProductA {
return new ProductA2();
}
createProductB(): ProductB {
return new ProductB2();
}
}

// 4. 创建一个接口
interface ProductA {
doSomething(): void;
}

// 5. 创建一个接口的实现类
class ProductA1 implements ProductA {
doSomething(): void {
console.log("ProductA1");
}
}

// 6. 创建一个接口的实现类
class ProductA2 implements ProductA {
doSomething(): void {
console.log("ProductA2");
}
}

// 7. 创建一个接口
interface ProductB {
doSomething(): void;
}

// 8. 创建一个接口的实现类
class ProductB1 implements ProductB {
doSomething(): void {
console.log("ProductB1");
}
}

// 9. 创建一个接口的实现类
class ProductB2 implements ProductB {
doSomething(): void {
console.log("ProductB2");
}
}

// 10. 创建一个抽象工厂
const createFactory = (type: string): Factory => {
if (type === "Factory1") {
return new Factory1();
}
if (type === "Factory2") {
return new Factory2();
}
return null;
};

// 11. 创建一个工厂
const factory1 = createFactory("Factory1");
const factory2 = createFactory("Factory2");

// 12. 通过工厂创建产品
const productA1 = factory1.createProductA();
const productB1 = factory2.createProductB();

// 13. 执行产品的方法
productA1.doSomething();
productB1.doSomething();

单例模式

概念

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供了一个全局访问点来访问该实例。

意图

确保一个类只有一个实例,并提供一个全局访问点来访问该实例。

何时使用

频繁创建和销毁全局使用的类实例的问题。当需要控制实例数目,节省系统资源时。

如何解决

检查系统是否已经存在该单例,如果存在则返回该实例;如果不存在则创建一个新实例。

关键

构造函数是私有的。
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
4、线程安全:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成实例被多次创建。
5、延迟初始化:实例在第一次调用 getInstance() 方法时创建。
6、序列化和反序列化:重写 readResolve 方法以确保反序列化时不会创建新的实例。
7、反射攻击:在构造函数中添加防护代码,防止通过反射创建新实例。
类加载器问题:注意复杂类加载环境可能导致的多个实例问题

优点与缺点

优点

1.内存中只有一个实例,减少内存开销,尤其是频繁创建和销毁实例时(如管理学院首页页面缓存)。
2.避免资源的多重占用(如写文件操作)。

缺点

1.没有接口,不能继承。
2.与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心实例化方式。

结构

单例模式包含以下几个主要角色:

  • 单例类:包含单例实例的类,通常将构造函数声明为私有。
  • 静态成员变量:用于存储单例实例的静态成员变量。
  • 获取实例方法:静态方法,用于获取单例实例。
  • 私有构造函数:防止外部直接实例化单例类。
  • 线程安全处理:确保在多线程环境下单例实例的创建是安全的。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 保证一个类仅有一个实例,并提供一个访问它的全局访问点
class Singleton {
private static instance: Singleton;
private constructor() {}
static getInstance() {
if (!Singleton.instance) {
Singleton.instance = new Singleton();
}
return Singleton.instance;
}
}
// 使用
const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();
console.log(instance1 === instance2); // true

建造者模式

概念

建造者模式是一种创建型设计模式,它允许你创建复杂对象的步骤与表示方式相分离。它的主要目的是将一个复杂对象的构建过程与其表示相分离,从而可以创建具有不同表示形式的对象。

意图

将一个复杂的构建过程与其表示相分离,使得同样的构建过程可以创建不同的表示。

何时使用

当一些基本部件不变,而其组合经常变化时。

如何解决

在软件系统中,一个复杂对象的创建通常由多个部分组成,这些部分的组合经常变化,但组合的算法相对稳定。将变与不变的部分分离开。

关键

1.建造者:创建并提供实例。
2.导演:管理建造出来的实例的依赖关系和控制构建过程。

优点与缺点

优点

1.分离构建过程和表示,使得构建过程更加灵活,可以构建不同的表示。
2.可以更好地控制构建过程,隐藏具体构建细节。
3.代码复用性高,可以在不同的构建过程中重复使用相同的建造者。

缺点

1.如果产品的属性较少,建造者模式可能会导致代码冗余。
2.增加了系统的类和对象数量。

结构

1.产品(Product):要构建的复杂对象。产品类通常包含多个部分或属性。
2.抽象建造者(Builder):定义了构建产品的抽象接口,包括构建产品的各个部分的方法。
3.具体建造者(Concrete Builder):实现抽象建造者接口,具体确定如何构建产品的各个部分,并负责返回最终构建的产品。
4.指导者(Director):负责调用建造者的方法来构建产品,指导者并不了解具体的构建过程,只关心产品的构建顺序和方式。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// 1. 产品类
class Product {
parts: string[] = [];

show() {
console.log(this.parts.join(','));
}
}

// 2. 抽象建造者
abstract class Builder {
abstract buildPartA(): void;
abstract buildPartB(): void;
abstract buildPartC(): void;
abstract getResult(): Product;
}

// 3. 具体建造者
class ConcreteBuilder extends Builder {
product: Product = new Product();

buildPartA() {
this.product.parts.push('partA');
}

buildPartB() {
this.product.parts.push('partB');
}

buildPartC() {
this.product.parts.push('partC');
}

getResult() {
return this.product;
}
}

// 4. 指导者
class Director {
builder: Builder;

constructor(builder: Builder) {
this.builder = builder;
}

construct() {
this.builder.buildPartA();
this.builder.buildPartB();
this.builder.buildPartC();
}
}

const builder = new ConcreteBuilder();
const director = new Director(builder);
director.construct();
const product = builder.getResult();
product.show();

原型模式

概念

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式之一。

这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。

意图

使用原型实例指定要创建对象的种类,并通过拷贝这些原型创建新的对象。

何时使用

1.系统应独立于产品的创建、构成和表示。
2.需要在运行时指定实例化的类,例如通过动态加载。
3.避免创建与产品类层次平行的工厂类层次。
4.类的实例只能有几种不同状态组合,克隆原型比手工实例化更方便。

如何解决

通过已有的一个原型对象,快速生成与原型对象相同的实例。在运行时动态建立和删除原型。

关键

隔离类对象的使用者和具体类型之间的耦合关系,要求”易变类”拥有稳定的接口。重写clone方法

优点与缺点

优点

1.性能提高
2.避免构造函数的约束

缺点

1.配备克隆方法需要全面考虑类的功能,对已有类可能较难实现,特别是处理不支持串行化的间接对象或含有循环结构的引用时
2.必须实现 Cloneable 接口

结构

1.原型接口(Prototype Interface):定义一个用于克隆自身的接口,通常包括一个 clone() 方法。
2.具体原型类(Concrete Prototype):实现原型接口的具体类,负责实际的克隆操作。这个类需要实现 clone() 方法,通常使用浅拷贝或深拷贝来复制自身。
3.客户端(Client):使用原型实例来创建新的对象。客户端调用原型对象的 clone() 方法来创建新的对象,而不是直接使用构造函数。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const Prototype = function (name) {
this.name = name;
};

Prototype.prototype = {
constructor: Prototype,

getName: function () {
return this.name;
},

clone: function () {
return new this.constructor(this.name);
}
};

const prototype = new Prototype('prototype');
const clone = prototype.clone();

console.log(prototype.name); // prototype
console.log(clone.name); // prototype

结构式模式

适配器模式

概念

适配器模式(Adapter Pattern)充当两个不兼容接口之间的桥梁,属于结构型设计模式。它通过一个中间件(适配器)将一个类的接口转换成客户期望的另一个接口,使原本不能一起工作的类能够协同工作。

这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。举个真实的例子,读卡器是作为内存卡和笔记本之间的适配器。您将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡。

意图

将一个类的接口转换为另一个接口,使得原本不兼容的类可以协同工作。

何时使用

1.需要使用现有类,但其接口不符合系统需求。
2.希望创建一个可复用的类,与多个不相关的类(包括未来可能引入的类)一起工作,这些类可能没有统一的接口。
3.通过接口转换,将一个类集成到另一个类系中

如何解决

继承或依赖:推荐使用依赖关系,而不是继承,以保持灵活性。

关键

适配器通过继承或依赖现有对象,并实现所需的目标接口。

优点与缺点

优点

1.促进了类之间的协同工作,即使它们没有直接的关联。
2.提高了类的复用性。
3.增加了类的透明度。
4.提供了良好的灵活性。

缺点

1.过度使用适配器可能导致系统结构混乱,难以理解和维护。

结构

1.目标接口(Target):定义客户需要的接口。
2.适配者类(Adaptee):定义一个已经存在的接口,这个接口需要适配。
3.适配器类(Adapter):实现目标接口,并通过组合或继承的方式调用适配者类中的方法,从而实现目标接口。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface Target {
request(): void;
}
class Adaptee {
specificRequest() {
console.log("Adaptee request");
}
}
class Adapter implements Target {
constructor(private adaptee: Adaptee) {}
request() {
this.adaptee.specificRequest();
}
}
const adaptee = new Adaptee();
const target = new Adapter(adaptee);
target.request();

桥接模式

概念

桥接(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化。这种类型的设计模式属于结构型模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。

这种模式涉及到一个作为桥接的接口,使得实体类的功能独立于接口实现类,这两种类型的类可被结构化改变而互不影响。

桥接模式的目的是将抽象与实现分离,使它们可以独立地变化,该模式通过将一个对象的抽象部分与它的实现部分分离,使它们可以独立地改变。它通过组合的方式,而不是继承的方式,将抽象和实现的部分连接起来。

意图

用于将抽象部分与实现部分分离,使得它们可以独立地变化。。

何时使用

当系统可能从多个角度进行分类,且每个角度都可能独立变化时,桥接模式是合适的。

如何解决

分离多角度分类:将不同角度的分类逻辑分离,允许它们独立变化。
减少耦合:降低抽象与实现之间的耦合度。

当系统需要在抽象化角色和具体化角色之间增加灵活性时,考虑使用桥接模式。
对于不希望使用继承或因多层次继承导致类数量急剧增加的系统,桥接模式特别适用。
当一个类存在两个独立变化的维度,且这两个维度都需要扩展时,使用桥接模式。

关键

抽象类:定义一个抽象类,作为系统的一部分。
实现类:定义一个或多个实现类,与抽象类通过聚合(而非继承)关联。

优点与缺点

优点

抽象与实现分离:提高了系统的灵活性和可维护性。
扩展能力强:可以独立地扩展抽象和实现。
实现细节透明:用户不需要了解实现细节。

缺点

理解与设计难度:桥接模式增加了系统的理解与设计难度。
聚合关联:要求开发者在抽象层进行设计与编程。

结构

1.抽象(Abstraction):定义抽象接口,通常包含对实现接口的引用。
2.扩展抽象(Refined Abstraction):对抽象的扩展,可以是抽象类的子类或具体实现类。
3.实现(Implementor):定义实现接口,提供基本操作的接口。
4.具体实现(Concrete Implementor):实现实现接口的具体类。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 1. 定义一个抽象类
abstract class Abstract {
constructor() {
console.log('Abstract Class')
}
abstract method(): void
}

// 2. 定义一个实现类
class Implement extends Abstract {
constructor() {
super()
console.log('Implement Class')
}
method() {
console.log('Implement Class Method')
}
}

// 3. 定义一个桥接类
class Bridge {
private abstract: Abstract
constructor(abstract: Abstract) {
this.abstract = abstract
}
method() {
console.log('Bridge Class Method')
this.abstract.method()
}
}

// 4. 实例化一个实现类
const implement = new Implement()

// 5. 实例化一个桥接类
const bridge = new Bridge(implement)
bridge.method()

过滤器模式

概念

过滤器模式(Filter Pattern)或标准模式(Criteria Pattern)是一种设计模式,这种模式允许开发人员使用不同的标准来过滤一组对象,通过逻辑运算以解耦的方式把它们连接起来。这种类型的设计模式属于结构型模式,它结合多个标准来获得单一标准。

意图

用于将对象的筛选过程封装起来,允许使用不同的筛选标准动态地筛选对象。

何时使用

当需要根据多个不同的条件或标准来筛选一组对象时,过滤器模式提供了一种灵活的方式来定义这些条件,避免在客户端代码中硬编码筛选逻辑。
当对象集合需要根据不同的标准进行筛选时。
当筛选逻辑可能变化,或者需要动态地组合多个筛选条件时。

如何解决

1.定义筛选接口:创建一个筛选接口,定义一个筛选方法。
2.实现具体筛选器:为每个筛选标准实现筛选接口,封装具体的筛选逻辑。
3.组合筛选器:允许筛选器之间进行组合,形成复杂的筛选逻辑。

关键

1.筛选接口:定义筛选方法,如 matches()。
2.具体筛选器类:实现筛选接口,封装具体的筛选逻辑。
3.组合筛选器:实现筛选器的组合逻辑,如逻辑与(AND)、逻辑或(OR)等。

优点与缺点

优点

1.封装性:筛选逻辑被封装在独立的筛选器对象中。
2.灵活性:可以动态地添加、修改或组合筛选条件。
3.可扩展性:容易添加新的筛选标准,无需修改现有代码。

缺点

1.复杂性:随着筛选条件的增加,系统可能变得复杂。
2.性能问题:如果筛选器组合过于复杂,可能会影响性能。

结构

1.过滤器接口(Filter/Criteria):定义一个接口,用于筛选对象。该接口通常包含一个方法,用于根据特定条件过滤对象。
2.具体过滤器类(Concrete Filter/Concrete Criteria):实现过滤器接口,具体定义筛选对象的条件和逻辑。
3.对象集合(Items/Objects to be filtered):要被过滤的对象集合。这些对象通常是具有共同属性的实例,例如一组人、一组产品等。
4.客户端(Client):使用具体过滤器类来筛选对象集合。客户端将对象集合和过滤器结合起来,以获得符合条件的对象

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 1.定义一个接口
interface Filter {
filter(data: string[]): string[]
}

// 2.定义实现接口的类
class FilterA implements Filter {
filter(data: string[]): string[] {
return data.filter((item) => item.includes('a'))
}
}

class FilterB implements Filter {
filter(data: string[]): string[] {
return data.filter((item) => item.includes('b'))
}
}

// 3.定义一个过滤器
class FilterChain {
filters: Filter[] = []
addFilter(filter: Filter) {
this.filters.push(filter)
}
filter(data: string[]) {
return this.filters.reduce((res, filter) => {
return filter.filter(res)
}, data)
}
}

// 4.使用过滤器
const filterChain = new FilterChain()
filterChain.addFilter(new FilterA())
filterChain.addFilter(new FilterB())
console.log(filterChain.filter(['a', 'b', 'c', 'ab']))

组合器模式

概念

组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。这种类型的设计模式属于结构型模式,它创建了对象组的树形结构。

这种模式创建了一个包含自己对象组的类。该类提供了修改相同对象组的方式。

意图

将对象组合成树形结构以表示”部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

何时使用

简化树形结构中对象的处理,无论它们是单个对象还是组合对象。
解耦客户端代码与复杂元素的内部结构,使得客户端可以统一处理所有类型的节点。
当需要表示对象的层次结构时,如文件系统或组织结构。
当希望客户端代码能够以一致的方式处理树形结构中的所有对象时。

如何解决

统一接口:定义一个接口,所有对象(树枝和叶子)都实现这个接口。
组合结构:树枝对象包含一个接口的引用列表,这些引用可以是叶子或树枝。

关键

1.Component接口:定义了所有对象必须实现的操作。
2.Leaf类:实现Component接口,代表树中的叶子节点。
3.Composite类:也实现Component接口,并包含其他Component对象的集合。

优点与缺点

优点

1.简化客户端代码:客户端可以统一处理所有类型的节点。
2.易于扩展:可以轻松添加新的叶子类型或树枝类型。

缺点

违反依赖倒置原则:组件的声明是基于具体类而不是接口,这可能导致代码的灵活性降低。

结构

1.组件(Component):
定义了组合中所有对象的通用接口,可以是抽象类或接口。它声明了用于访问和管理子组件的方法,包括添加、删除、获取子组件等。
2.叶子节点(Leaf):
表示组合中的叶子节点对象,叶子节点没有子节点。它实现了组件接口的方法,但通常不包含子组件。
3.复合节点(Composite):
表示组合中的复合对象,复合节点可以包含子节点,可以是叶子节点,也可以是其他复合节点。它实现了组件接口的方法,包括管理子组件的方法。
4.客户端(Client):

通过组件接口与组合结构进行交互,客户端不需要区分叶子节点和复合节点,可以一致地对待整体和部分。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 1.定义一个接口
interface IMenu {
name: string;
show(): void;
}

// 2.定义一个组合器类
class Menu implements IMenu {
name: string;
children: IMenu[];

constructor(name: string) {
this.name = name;
this.children = [];
}

add(menu: IMenu) {
this.children.push(menu);
}

remove(menu: IMenu) {
const index = this.children.findIndex((item) => item === menu);
this.children.splice(index, 1);
}

show() {
console.log(this.name);
this.children.forEach((item) => item.show());
}
}

// 3.实例化组合器类
const menu1 = new Menu('菜单1');
const menu2 = new Menu('菜单2');
const menu3 = new Menu('菜单3');
menu1.add(menu2);
menu2.add(menu3);
menu1.show(); // 菜单1 菜单2 菜单3

装饰器模式

概念

装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

装饰器模式通过将对象包装在装饰器类中,以便动态地修改其行为。

这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

意图

动态地给一个对象添加额外的职责,同时不改变其结构。装饰器模式提供了一种灵活的替代继承方式来扩展功能。

何时使用

1.避免通过继承引入静态特征,特别是在子类数量急剧膨胀的情况下。
2.允许在运行时动态地添加或修改对象的功能。
3.当需要在不增加大量子类的情况下扩展类的功能。
4.当需要动态地添加或撤销对象的功能。

如何解决

1.定义组件接口:创建一个接口,规定可以动态添加职责的对象的标准。
2.创建具体组件:实现该接口的具体类,提供基本功能。
3.创建抽象装饰者:实现同样的接口,持有一个组件接口的引用,可以在任何时候动态地添加功能。
4.创建具体装饰者:扩展抽象装饰者,添加额外的职责

关键

1.Component接口:定义了可以被装饰的对象的标准。
2.ConcreteComponent类:实现Component接口的具体类。
3.Decorator抽象类:实现Component接口,并包含一个Component接口的引用。
4.ConcreteDecorator类:扩展Decorator类,添加额外的功能。

优点与缺点

优点

1.低耦合:装饰类和被装饰类可以独立变化,互不影响。
2.灵活性:可以动态地添加或撤销功能。
3.替代继承:提供了一种继承之外的扩展对象功能的方式。

缺点

复杂性:多层装饰可能导致系统复杂性增加。

结构

1.抽象组件(Component):定义了原始对象和装饰器对象的公共接口或抽象类,可以是具体组件类的父类或接口。
2.具体组件(Concrete Component):是被装饰的原始对象,它定义了需要添加新功能的对象。
3.抽象装饰器(Decorator):继承自抽象组件,它包含了一个抽象组件对象,并定义了与抽象组件相同的接口,同时可以通过组合方式持有其他装饰器对象。
4.具体装饰器(Concrete Decorator):实现了抽象装饰器的接口,负责向抽象组件添加新的功能。具体装饰器通常会在调用原始对象的方法之前或之后执行自己的操作。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 首先,定义一个接口,该接口包含一个方法,该方法将被装饰器类实现。
interface Component {
operation(): string;
}

// 然后,创建一个具体的实现接口的类。
class ConcreteComponent implements Component {
public operation(): string {
return "ConcreteComponent";
}
}

// 接着,定义一个装饰器类,该类将包含一个被装饰对象的引用,并通过将被装饰对象的方法调用包装在自己的方法中来增强被装饰对象的行为。
class Decorator implements Component {
protected component: Component;

constructor(component: Component) {
this.component = component;
}

public operation(): string {
return this.component.operation();
}
}

// 最后,创建一个具体的装饰器类,该类将实现装饰器类并添加一些新的行为。
class ConcreteDecorator extends Decorator {
public operation(): string {
return `ConcreteDecorator(${super.operation()})`;
}
}

// 使用装饰器模式
const simple = new ConcreteComponent();
console.log(simple.operation()); // ConcreteComponent

const decorator = new ConcreteDecorator(simple);
console.log(decorator.operation()); // ConcreteDecorator(ConcreteComponent)

外观模式

概念

外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。

这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用。

意图

为一个复杂的子系统提供一个一致的高层接口。这样,客户端代码就可以通过这个简化的接口与子系统交互,而不需要了解子系统内部的复杂性。

何时使用

1.降低客户端与复杂子系统之间的耦合度。
2.简化客户端对复杂系统的操作,隐藏内部实现细节。
3.当客户端不需要了解系统内部的复杂逻辑和组件交互时。
4.当需要为整个系统定义一个清晰的入口点时。

如何解决

1.创建外观类:定义一个类(外观),作为客户端与子系统之间的中介。
2.封装子系统操作:外观类将复杂的子系统操作封装成简单的方法。

关键

1.Facade类:提供高层接口,简化客户端与子系统的交互。
2.子系统类:实现具体的业务逻辑,被Facade类调用。

优点与缺点

优点

1.减少依赖:客户端与子系统之间的依赖减少。
2.提高灵活性:子系统的内部变化不会影响客户端。
3.增强安全性:隐藏了子系统的内部实现,只暴露必要的操作。

缺点

违反开闭原则:对子系统的修改可能需要对外观类进行相应的修改。

结构

1.外观(Facade):
提供一个简化的接口,封装了系统的复杂性。外观模式的客户端通过与外观对象交互,而无需直接与系统的各个组件打交道。
2.子系统(Subsystem):
由多个相互关联的类组成,负责系统的具体功能。外观对象通过调用这些子系统来完成客户端的请求。
3.客户端(Client):
使用外观对象来与系统交互,而不需要了解系统内部的具体实现。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// 1. 创建一个接口。
interface Shape {
draw(): void;
}

// 2. 创建实现接口的实体类。
class Circle implements Shape {
draw() {
console.log('Circle::draw()');
}
}

class Rectangle implements Shape {
draw() {
console.log('Rectangle::draw()');
}
}

class Square implements Shape {
draw() {
console.log('Square::draw()');
}
}

// 3. 创建一个外观类。
class ShapeMaker {
private circle: Shape;
private rectangle: Shape;
private square: Shape;

constructor() {
this.circle = new Circle();
this.rectangle = new Rectangle();
this.square = new Square();
}

drawCircle() {
this.circle.draw();
}

drawRectangle() {
this.rectangle.draw();
}

drawSquare() {
this.square.draw();
}
}

// 4. 使用该外观类画出各种类型的形状。
const shapeMaker = new ShapeMaker();

shapeMaker.drawCircle();
shapeMaker.drawRectangle();
shapeMaker.drawSquare();

享元模式

概念

享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。

享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。

意图

通过共享对象来减少创建大量相似对象时的内存消耗。。

何时使用

1.避免因创建大量对象而导致的内存溢出问题。
2.通过共享对象,提高内存使用效率。
3.当系统中存在大量相似或相同的对象。
4.对象的创建和销毁成本较高。
5.对象的状态可以外部化,即对象的部分状态可以独立于对象本身存在。

如何解决

1.定义享元接口:创建一个享元接口,规定可以共享的状态。
2.创建具体享元类:实现该接口的具体类,包含内部状态。
3.使用享元工厂:创建一个工厂类,用于管理享元对象的创建和复用。

关键

HashMap:使用哈希表存储已经创建的享元对象,以便快速检索

优点与缺点

优点

1.减少内存消耗:通过共享对象,减少了内存中对象的数量。
2.提高效率:减少了对象创建的时间,提高了系统效率。

缺点

1.增加系统复杂度:需要分离内部状态和外部状态,增加了设计和实现的复杂性。
2.线程安全问题:如果外部状态处理不当,可能会引起线程安全问题。

结构

1.享元工厂(Flyweight Factory):
负责创建和管理享元对象,通常包含一个池(缓存)用于存储和复用已经创建的享元对象。
2.具体享元(Concrete Flyweight):
实现了抽象享元接口,包含了内部状态和外部状态。内部状态是可以被共享的,而外部状态则由客户端传递。
3.抽象享元(Flyweight):
定义了具体享元和非共享享元的接口,通常包含了设置外部状态的方法。
4.客户端(Client):
使用享元工厂获取享元对象,并通过设置外部状态来操作享元对象。客户端通常不需要关心享元对象的具体实现。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 创建享元工厂
type FlyweightFactory = {
flyweights: Record<string, any>
get: (key: string) => any
create: (key: string) => any
}
const flyweightFactory: FlyweightFactory = {
flyweights: {},
get: (key: string) => flyweightFactory.flyweights[key],
create: (key: string) => {
flyweightFactory.flyweights[key] = {
key,
operation: () => {
console.log(key)
}
}
return flyweightFactory.flyweights[key]
}
}
const flyweight1 = flyweightFactory.create('flyweight1')
const flyweight2 = flyweightFactory.create('flyweight2')
const flyweight3 = flyweightFactory.create('flyweight3')
flyweight1.operation()
flyweight2.operation()
flyweight3.operation()

代理模式

概念

在代理模式(Proxy Pattern)中,一个类代表另一个类的功能,这种类型的设计模式属于结构型模式。

代理模式通过引入一个代理对象来控制对原对象的访问。代理对象在客户端和目标对象之间充当中介,负责将客户端的请求转发给目标对象,同时可以在转发请求前后进行额外的处理。

在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。

意图

为其他对象提供一种代理以控制对这个对象的访问。

何时使用

1.代理模式解决的是在直接访问某些对象时可能遇到的问题,例如对象创建成本高、需要安全控制或远程访问等。
2.当需要在访问一个对象时进行一些控制或额外处理时。

如何解决

1.增加中间层:创建一个代理类,作为真实对象的中间层。
2.代理与真实对象组合:代理类持有真实对象的引用,并在访问时进行控制

关键

1.代理类:实现与真实对象相同的接口,并添加额外的控制逻辑。
2.真实对象:实际执行任务的对象。

优点与缺点

优点

1.职责分离:代理模式将访问控制与业务逻辑分离。
2.扩展性:可以灵活地添加额外的功能或控制。
3.智能化:可以智能地处理访问请求,如延迟加载、缓存等。

缺点

1.性能开销:增加了代理层可能会影响请求的处理速度。
2.实现复杂性:某些类型的代理模式实现起来可能较为复杂。

结构

1.抽象主题(Subject):
定义了真实主题和代理主题的共同接口,这样在任何使用真实主题的地方都可以使用代理主题。
2.真实主题(Real Subject):
实现了抽象主题接口,是代理对象所代表的真实对象。客户端直接访问真实主题,但在某些情况下,可以通过代理主题来间接访问。
3.代理(Proxy):
实现了抽象主题接口,并持有对真实主题的引用。代理主题通常在真实主题的基础上提供一些额外的功能,例如延迟加载、权限控制、日志记录等。
4.客户端(Client):
使用抽象主题接口来操作真实主题或代理主题,不需要知道具体是哪一个实现类。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 代理模式
class RealSubject {
request() {
console.log("RealSubject");
}
}

class Proxy {
realSubject: RealSubject;
constructor() {
this.realSubject = new RealSubject();
}
request() {
this.realSubject.request();
}
}

const proxy = new Proxy();
proxy.request()

行为模式

责任链模式

概念

责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。

责任链模式通过将多个处理器(处理对象)以链式结构连接起来,使得请求沿着这条链传递,直到有一个处理器处理该请求为止。

责任链模式允许多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。

意图

允许将请求沿着处理者链传递,直到请求被处理为止。

何时使用

  • 解耦请求发送者和接收者,使多个对象都有可能接收请求,而发送者不需要知道哪个对象会处理它。
  • 当有多个对象可以处理请求,且具体由哪个对象处理由运行时决定时。
  • 当需要向多个对象中的一个提交请求,而不想明确指定接收者时

如何解决

1.定义处理者接口:所有处理者必须实现同一个接口。
2.创建具体处理者:实现接口的具体类,包含请求处理逻辑和指向链中下一个处理者的引用。

关键

1.Handler接口:定义一个方法用于处理请求。
2.ConcreteHandler类:实现Handler接口,包含请求处理逻辑和对下一个处理者的引用。

优点与缺点

优点

1.降低耦合度:发送者和接收者之间解耦。
2.简化对象:对象不需要知道链的结构。
3.灵活性:通过改变链的成员或顺序,动态地新增或删除责任。
4.易于扩展:增加新的请求处理类很方便。

缺点

1.请求未被处理:不能保证请求一定会被链中的某个处理者接收。
2.性能影响:可能影响系统性能,且调试困难,可能导致循环调用。
3.难以观察:运行时特征不明显,可能妨碍除错。

结构

1.抽象处理者(Handler):
定义一个处理请求的接口,通常包含一个处理请求的方法(如 handleRequest)和一个指向下一个处理者的引用(后继者)。
2.具体处理者(ConcreteHandler):
实现了抽象处理者接口,负责处理请求。如果能够处理该请求,则直接处理;否则,将请求传递给下一个处理者。
3.客户端(Client):
创建处理者对象,并将它们连接成一条责任链。通常,客户端只需要将请求发送给责任链的第一个处理者,无需关心请求的具体处理过程。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
type Handler = {
setNext: (handler: Handler) => Handler;
handle: (request: number) => string;
};

function handleRequest(request: number): string {
const handler1: Handler = {
setNext: (handler: Handler): Handler => {
this.nextHandler = handler;
return handler;
},
handle: (request: number): string => {
if (request < 0) {
return "负数";
}
if (this.nextHandler) {
return this.nextHandler.handle(request);
}
return "正数";
},
};

const handler2: Handler = {
setNext: (handler: Handler): Handler => {
this.nextHandler = handler;
return handler;
},
handle: (request: number): string => {
if (request % 2 === 0) {
return "偶数";
}
if (this.nextHandler) {
return this.nextHandler.handle(request);
}
return "奇数";
},
};

handler1.setNext(handler2);
return handler1.handle(request);
}

handleRequest(3); // 奇数
handleRequest(-1); // 负数
handleRequest(4); // 偶数

命令模式

概念

命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。

命令模式将一个请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。

意图

将请求封装为一个对象,允许用户使用不同的请求对客户端进行参数化。

何时使用

解决在软件系统中请求者和执行者之间的紧耦合问题,特别是在需要对行为进行记录、撤销/重做或事务处理等场景。当需要对行为进行记录、撤销/重做或事务处理时,使用命令模式来解耦请求者和执行者。

如何解决

1.定义命令接口:所有命令必须实现的接口。
2.创建具体命令:实现命令接口的具体类,包含执行请求的方法。
3.调用者:持有命令对象并触发命令的执行。
4.接收者:实际执行命令的对象。

关键

1.接收者(Receiver):执行命令的实际对象。
2.命令(Command):定义执行命令的接口。
3.调用者(Invoker):使用命令对象的入口点。

优点与缺点

优点

1.降低耦合度:请求者和执行者之间的耦合度降低。
2.易于扩展:新命令可以很容易地添加到系统中。

缺点

过多命令类:系统可能会有过多的具体命令类,增加系统的复杂度。

结构

1.命令(Command):
定义了执行操作的接口,通常包含一个 execute 方法,用于调用具体的操作。
2.具体命令(ConcreteCommand):
实现了命令接口,负责执行具体的操作。它通常包含了对接收者的引用,通过调用接收者的方法来完成请求的处理。
3.接收者(Receiver):
知道如何执行与请求相关的操作,实际执行命令的对象。
4.调用者/请求者(Invoker):
发送命令的对象,它包含了一个命令对象并能触发命令的执行。调用者并不直接处理请求,而是通过将请求传递给命令对象来实现。
5.客户端(Client):
创建具体命令对象并设置其接收者,将命令对象交给调用者执行。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 1. 创建一个命令接口
interface Command {
execute(): void;
}
// 2. 创建一个请求类
class Receiver {
action(): void {
console.log('请求已收到');
}
}
// 3. 创建具体的命令类
class ConcreteCommand implements Command {
private receiver: Receiver;
constructor(receiver: Receiver) {
this.receiver = receiver;
}
execute(): void {
console.log('请求已发送');
this.receiver.action();
}
}
// 4. 创建一个请求者
class Invoker {
private command: Command;
constructor(command: Command) {
this.command = command;
}
action(): void {
this.command.execute();
}
}
// 5. 使用
const receiver = new Receiver();
const command = new ConcreteCommand(receiver);
const invoker = new Invoker(command);
invoker.action();

解释器模式

概念

解释器模式(Interpreter Pattern)提供了评估语言的语法或表达式的方式,它属于行为型模式。
解释器模式给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
这种模式被用在 SQL 解析、符号处理引擎等。

意图

定义一种语言的文法表示,并创建一个解释器,该解释器能够解释该语言中的句子。

何时使用

解释器模式用于构建一个能够解释特定语言或文法的句子的解释器。当某一特定类型的问题频繁出现,并且可以通过一种简单的语言来表达这些问题的实例时。

如何解决

1.定义文法:明确语言的终结符和非终结符。
2.构建语法树:根据语言的句子构建对应的语法树结构。
3.创建环境类:包含解释过程中所需的全局信息,通常是一个HashMap。

关键

1.终结符与非终结符:定义语言的文法结构。
2.环境类:存储解释过程中需要的外部环境信息。

优点与缺点

优点

1.可扩展性好:容易添加新的解释表达式的方式。
2.灵活性:可以根据需要轻松扩展或修改文法。
3.易于实现简单文法:对于简单的语言,实现起来相对容易

缺点

1.使用场景有限:只适用于适合使用解释的简单文法。
2.维护困难:对于复杂的文法,维护和扩展变得困难。
3.类膨胀:可能会产生很多类,每个文法规则对应一个类。
4.递归调用:解释器模式通常使用递归调用,这可能难以理解和跟踪。

结构

1.抽象表达式(Abstract Expression):定义了解释器的抽象接口,声明了解释操作的方法,通常是一个抽象类或接口。
2.终结符表达式(Terminal Expression):实现了抽象表达式接口的终结符表达式类,用于表示语言中的终结符(如变量、常量等),并实现了对应的解释操作。
3.非终结符表达式(Non-terminal Expression):实现了抽象表达式接口的非终结符表达式类,用于表示语言中的非终结符(如句子、表达式等),并实现了对应的解释操作。
4.上下文(Context):包含解释器之外的一些全局信息,在解释过程中提供给解释器使用,通常用于存储变量的值、保存解释器的状态等。
5.客户端(Client):创建并配置具体的解释器对象,并将需要解释的表达式传递给解释器进行解释。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// 1. 创建一个表达式接口
interface Expression {
interpret: (context: string) => boolean;
}

// 2. 创建一个解释器实现类
class TerminalExpression implements Expression {
private data: string;

constructor(data: string) {
this.data = data;
}

public interpret(context: string): boolean {
return context.includes(this.data);
}
}

class OrExpression implements Expression {
private expr1: Expression;
private expr2: Expression;

constructor(expr1: Expression, expr2: Expression) {
this.expr1 = expr1;
this.expr2 = expr2;
}

public interpret(context: string): boolean {
return this.expr1.interpret(context) || this.expr2.interpret(context);
}
}

class AndExpression implements Expression {
private expr1: Expression;
private expr2: Expression;

constructor(expr1: Expression, expr2: Expression) {
this.expr1 = expr1;
this.expr2 = expr2;
}

public interpret(context: string): boolean {
return this.expr1.interpret(context) && this.expr2.interpret(context);
}
}

// 3. 使用 Expression 类来创建规则并解析它们
const robert: Expression = new TerminalExpression("Robert");
const john: Expression = new TerminalExpression("John");
const julie: Expression = new TerminalExpression("Julie");
const marrie: Expression = new TerminalExpression("Marrie");

const isSingle: Expression = new OrExpression(robert, john);
const isMarried: Expression = new AndExpression(julie, marrie);

迭代器模式

概念

迭代器模式(Iterator Pattern)提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。

意图

允许顺序访问一个聚合对象中的元素,同时不暴露对象的内部表示。

何时使用

提供一种统一的方法来遍历不同的聚合对象。当需要遍历一个聚合对象,而又不希望暴露其内部结构时。

如何解决

1.定义迭代器接口:包含hasNext()和next()等方法,用于遍历元素。
2.创建具体迭代器:实现迭代器接口,定义如何遍历特定的聚合对象。
3.聚合类:定义一个接口用于返回一个迭代器对象。

关键

1.迭代器接口:规定了遍历元素的方法。
2.具体迭代器:实现了迭代器接口,包含遍历逻辑。

优点与缺点

优点

1.支持多种遍历方式:不同的迭代器可以定义不同的遍历方式。
2.简化聚合类:聚合类不需要关心遍历逻辑。
3.多遍历支持:可以同时对同一个聚合对象进行多次遍历。
4.扩展性:增加新的聚合类和迭代器类都很方便,无需修改现有代码。

缺点

系统复杂性:每增加一个聚合类,就需要增加一个对应的迭代器类,增加了类的数量。

结构

1.迭代器接口(Iterator):定义了访问和遍历聚合对象中各个元素的方法,通常包括获取下一个元素、判断是否还有元素、获取当前位置等方法。
2.具体迭代器(Concrete Iterator):实现了迭代器接口,负责对聚合对象进行遍历和访问,同时记录遍历的当前位置。
3.聚合对象接口(Aggregate):定义了创建迭代器对象的接口,通常包括一个工厂方法用于创建迭代器对象。
4.具体聚合对象(Concrete Aggregate):实现了聚合对象接口,负责创建具体的迭代器对象,并提供需要遍历的数据。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
type Iterator<T> = {
current: () => T,
next: () => T,
hasNext: () => boolean,
}
type List<T> = {
data: T[],
iterator: () => Iterator<T>
}
const list: List<number> = {
data: [1, 2, 3, 4, 5],
iterator() {
let index = 0
return {
current() {
return list.data[index]
},
next() {
return list.data[index++]
},
hasNext() {
return index < list.data.length
}
}
}
}
const iterator = list.iterator()
while (iterator.hasNext()) {
console.log(iterator.next())
}

中介者模式

概念

中介者模式(Mediator Pattern)是用来降低多个对象和类之间的通信复杂性,属于行为型模式。
中介者模式定义了一个中介对象来封装一系列对象之间的交互。中介者使各对象之间不需要显式地相互引用,从而使其耦合松散,且可以独立地改变它们之间的交互。

意图

通过引入一个中介者对象来封装和协调多个对象之间的交互,从而降低对象间的耦合度。

何时使用

解决对象间复杂的一对多关联问题,避免对象之间的高度耦合,简化系统结构。当系统中多个类相互耦合,形成网状结构时。

如何解决

1.定义中介者接口:规定中介者必须实现的接口。
2.创建具体中介者:实现中介者接口,包含协调各同事对象交互的逻辑。
3.定义同事类:各个对象不需要显式地相互引用,而是通过中介者来进行交互。

关键

1.中介者:封装了对象间的交互逻辑。
2.同事类:通过中介者进行通信。

优点与缺点

优点

1.降低复杂度:将多个对象间的一对多关系转换为一对一关系。
2.解耦:对象之间不再直接引用,通过中介者进行交互。
3.符合迪米特原则:对象只需知道中介者,不需要知道其他对象。

缺点

中介者复杂性:中介者可能会变得庞大和复杂,难以维护。

结构

1.中介者(Mediator):定义了一个接口用于与各个同事对象通信,并管理各个同事对象之间的关系。通常包括一个或多个事件处理方法,用于处理各种交互事件。
2.具体中介者(Concrete Mediator):实现了中介者接口,负责实现各个同事对象之间的通信逻辑。它会维护一个对各个同事对象的引用,并协调它们的交互。
3.同事对象(Colleague):定义了一个接口,用于与中介者进行通信。通常包括一个发送消息的方法,以及一个接收消息的方法。
4.具体同事对象(Concrete Colleague):实现了同事对象接口,是真正参与到交互中的对象。它会将自己的消息发送给中介者,由中介者转发给其他同事对象。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// 同事类
class Colleague {
mediator: Mediator
constructor(mediator: Mediator) {
this.mediator = mediator
}
send(message: string) {
this.mediator.send(message, this)
}
receive(message: string) {
console.log(message)
}
}

// 具体同事类
class ConcreteColleague1 extends Colleague {
constructor(mediator: Mediator) {
super(mediator)
}
send(message: string) {
super.send(message)
}
receive(message: string) {
super.receive(message)
}
}

class ConcreteColleague2 extends Colleague {
constructor(mediator: Mediator) {
super(mediator)
}
send(message: string) {
super.send(message)
}
receive(message: string) {
super.receive(message)
}
}

// 中介者
class Mediator {
colleague1: ConcreteColleague1
colleague2: ConcreteColleague2
constructor() {
this.colleague1 = new ConcreteColleague1(this)
this.colleague2 = new ConcreteColleague2(this)
}
send(message: string, colleague: Colleague) {
if (colleague === this.colleague1) {
this.colleague2.receive(message)
} else {
this.colleague1.receive(message)
}
}
}

// 使用
const mediator = new Mediator()
mediator.colleague1.send('hello')
mediator.colleague2.send('world')

备忘录模式

概念

备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象,备忘录模式属于行为型模式。
备忘录模式允许在不破坏封装性的前提下,捕获和恢复对象的内部状态

意图

在不破坏封装性的前提下,捕获一个对象的内部状态,并允许在对象之外保存和恢复这些状态。

何时使用

允许捕获并保存一个对象的内部状态,以便在将来可以恢复到该状态,实现撤销和回滚操作。当需要提供一种撤销机制,允许用户回退到之前的状态时。

如何解决

1.创建备忘录类:用于存储和封装对象的状态。
2.创建发起人角色:负责创建备忘录,并根据需要恢复状态。
3.创建备忘录管理类(可选):负责管理所有备忘录对象。

关键

1.备忘录:存储发起人的状态信息。
2.发起人:创建备忘录,并根据备忘录恢复状态。

优点与缺点

优点

1.提供状态恢复机制:允许用户方便地回到历史状态。
2.封装状态信息:用户不需要关心状态的保存细节。

缺点

资源消耗:如果对象的状态复杂,保存状态可能会占用较多资源。

结构

1.备忘录(Memento):负责存储原发器对象的内部状态。备忘录可以保持原发器的状态的一部分或全部信息。
2.原发器(Originator):创建一个备忘录对象,并且可以使用备忘录对象恢复自身的内部状态。原发器通常会在需要保存状态的时候创建备忘录对象,并在需要恢复状态的时候使用备忘录对象。
3.负责人(Caretaker):负责保存备忘录对象,但是不对备忘录对象进行操作或检查。负责人只能将备忘录传递给其他对象。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 1. 创建备忘录对象
type Memento = {
state: string;
};

// 2. 创建发起人对象
class Originator {
state: string;

constructor(state: string) {
this.state = state;
}

// 创建备忘录
createMemento(): Memento {
return {
state: this.state,
};
}

// 恢复备忘录
restoreMemento(memento: Memento) {
this.state = memento.state;
}
}

// 3. 创建管理者对象
class Caretaker {
memento: Memento;

setMemento(memento: Memento) {
this.memento = memento;
}

getMemento() {
return this.memento;
}
}

const originator = new Originator("123");
const caretaker = new Caretaker();
caretaker.setMemento(originator.createMemento());
originator.state = "456";
console.log(originator.state);
originator.restoreMemento(caretaker.getMemento());
console.log(originator.state);

观察者模式

概念

观察者模式是一种行为型设计模式,它定义了一种一对多的依赖关系,当一个对象的状态发生改变时,其所有依赖者都会收到通知并自动更新。

当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象。观察者模式属于行为型模式。

意图

创建了对象间的一种一对多的依赖关系,当一个对象状态改变时,所有依赖于它的对象都会得到通知并自动更新。

何时使用

当一个对象的状态变化需要同时更新其他对象时。

如何解决

1.定义观察者接口:包含一个更新方法。
2.创建具体观察者:实现观察者接口,定义接收到通知时的行为。
3.定义主题接口:包含添加、删除和通知观察者的方法。
4.创建具体主题:实现主题接口,管理观察者列表,并在状态改变时通知它们。

关键

观察者列表:在主题中维护一个观察者列表。

优点与缺点

优点

1.抽象耦合:观察者和主题之间是抽象耦合的。
2.触发机制:建立了一套状态改变时的触发和通知机制。

缺点

1.性能问题:如果观察者众多,通知过程可能耗时。
2.循环依赖:可能导致循环调用和系统崩溃。
3.缺乏变化详情:观察者不知道主题如何变化,只知道变化发生。

结构

1.主题(Subject):也称为被观察者或可观察者,它是具有状态的对象,并维护着一个观察者列表。主题提供了添加、删除和通知观察者的方法。
2.观察者(Observer):观察者是接收主题通知的对象。观察者需要实现一个更新方法,当收到主题的通知时,调用该方法进行更新操作。
3.具体主题(Concrete Subject):具体主题是主题的具体实现类。它维护着观察者列表,并在状态发生改变时通知观察者。
4.具体观察者(Concrete Observer):具体观察者是观察者的具体实现类。它实现了更新方法,定义了在收到主题通知时需要执行的具体操作。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 1. 定义观察者
type Observer = (data: any) => void
// 2. 定义主题
class Subject {
private observers: Observer[] = []
// 添加观察者
addObserver(observer: Observer) {
this.observers.push(observer)
}
// 通知所有观察者
notify(data: any) {
this.observers.forEach(observer => observer(data))
}
}
// 3. 定义观察者
const observer1: Observer = data => {
console.log('观察者1收到数据:', data)
}
const observer2: Observer = data => {
console.log('观察者2收到数据:', data)
}
// 4. 创建主题
const subject = new Subject()
// 5. 添加观察者
subject.addObserver(observer1)
subject.addObserver(observer2)
// 6. 通知所有观察者
subject.notify('Hello')

状态模式

概念

在状态模式(State Pattern)中,类的行为是基于它的状态改变的,这种类型的设计模式属于行为型模式。

在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。

状态模式允许对象在内部状态改变时改变其行为,使得对象在不同的状态下有不同的行为表现。通过将每个状态封装成独立的类,可以避免使用大量的条件语句来实现状态切换。

意图

允许一个对象在其内部状态改变时改变其行为,看起来就像是改变了其类一样。

何时使用

状态模式解决对象行为依赖于其状态的问题,使得对象可以在状态变化时切换行为。当代码中存在大量条件语句,且这些条件语句依赖于对象的状态时。

如何解决

1.定义状态接口:声明一个或多个方法,用于封装具体状态的行为。
2.创建具体状态类:实现状态接口,根据状态的不同实现具体的行为。
3.定义上下文类:包含一个状态对象的引用,并在状态改变时更新其行为。

关键

1.状态接口:声明行为方法。
2.具体状态类:实现状态接口,封装具体行为。
3.上下文类:维护一个状态对象,并提供方法以改变其状态。

优点与缺点

优点

1.封装状态转换规则:将状态转换逻辑封装在状态对象内部。
2.易于扩展:增加新的状态类不会影响现有代码。
3.集中状态相关行为:将所有与特定状态相关的行为集中到一个类中。
4.简化条件语句:避免使用大量的条件语句来切换行为。
5.状态共享:允许多个上下文对象共享同一个状态对象。

缺点

1.增加类和对象数量:每个状态都需要一个具体的状态类。
2.实现复杂:模式结构和实现相对复杂。
3.开闭原则支持不足:增加新状态或修改状态行为可能需要修改现有代码。

结构

1.上下文(Context):定义了客户感兴趣的接口,并维护一个当前状态对象的引用。上下文可以通过状态对象来委托处理状态相关的行为。
2.状态(State):定义了一个接口,用于封装与上下文相关的一个状态的行为。
3.具体状态(Concrete State):实现了状态接口,负责处理与该状态相关的行为。具体状态对象通常会在内部维护一个对上下文对象的引用,以便根据不同的条件切换到不同的状态。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 状态接口
interface State {
handle(context: Context): void;
}

// 灯关闭状态
class OffState implements State {
handle(context: Context) {
console.log('关灯');
context.setState(new OnState());
}
}

// 灯打开状态
class OnState implements State {
handle(context: Context) {
console.log('开灯');
context.setState(new OffState());
}
}

// 灯对象
class Context {
private state!: State;
constructor() {
this.state = new OffState();
}
setState(state: State) {
this.state = state;
}
request() {
this.state.handle(this);
}
}

// 使用
const context = new Context();
context.request(); // 开灯
context.request(); // 关灯

空对象模式

概念

在空对象模式(Null Object Pattern)中,一个空对象取代 NULL 对象实例的检查。Null 对象不是检查空值,而是反应一个不做任何动作的关系。这样的 Null 对象也可以在数据不可用的时候提供默认的行为。

在空对象模式中,我们创建一个指定各种要执行的操作的抽象类和扩展该类的实体类,还创建一个未对该类做任何实现的空对象类,该空对象类将无缝地使用在需要检查空值的地方。

意图

使用一个空对象代替 null 值,这个空对象实现了相同的接口,但对请求不做任何操作或提供默认操作。

何时使用

当系统中需要处理null对象,但又希望避免null检查或处理null值时。

如何解决

1.定义协议:定义一个协议或接口,规定需要实现的行为。
2.创建具体对象:实现协议的具体对象,提供实际的行为。
3.创建空对象:也实现相同的协议,但提供”空”的实现,即不执行任何有意义的操作。

关键

1.协议或接口:规定对象需要实现的方法。
2.具体对象:实现了协议,包含实际的业务逻辑。
3.空对象:实现了协议,但方法实现为空或默认行为。

优点与缺点

优点

1.避免空值检查:消除了代码中的null值检查。
2.简化客户端代码:客户端可以无视对象是否为空,直接调用方法。
3.扩展性:添加新的具体对象对客户端透明,无需修改现有代码。

缺点

1.可能隐藏错误:使用空对象可能隐藏了错误或异常情况,导致难以调试。
2.增加设计复杂性:需要为每个可能返回null的接口实现一个空对象。

结构

1.抽象对象(Abstract Object):定义了客户端所期望的接口。这个接口可以是一个抽象类或接口。
2.具体对象(Concrete Object):实现了抽象对象接口的具体类。这些类提供了真实的行为。
3.空对象(Null Object):实现了抽象对象接口的空对象类。这个类提供了默认的无效行为,以便在对象不可用或不可用时使用。它可以作为具体对象的替代者,在客户端代码中代替空值检查。

代码示例

1
2
3
4
5
type EmptyObject = {
[key: string]: any
}
const emptyObject: EmptyObject = {}
console.log(emptyObject)

策略模式

概念

在策略模式(Strategy Pattern)中一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。
在策略模式定义了一系列算法或策略,并将每个算法封装在独立的类中,使得它们可以互相替换。通过使用策略模式,可以在运行时根据需要选择不同的算法,而不需要修改客户端代码。
在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。

意图

将每个算法封装起来,使它们可以互换使用。

何时使用

当一个系统中有许多类,它们之间的区别仅在于它们的行为时。

如何解决

1.定义策略接口:所有策略类都将实现这个统一的接口。
2.创建具体策略类:每个策略类封装一个具体的算法或行为。
3.上下文类:包含一个策略对象的引用,并通过该引用调用策略。

关键

1.策略接口:规定了所有策略类必须实现的方法。
2.具体策略类:实现了策略接口,包含具体的算法实现。

优点与缺点

优点

1.算法切换自由:可以在运行时根据需要切换算法。
2.避免多重条件判断:消除了复杂的条件语句。
3.扩展性好:新增算法只需新增一个策略类,无需修改现有代码。

缺点

1.策略类数量增多:每增加一个算法,就需要增加一个策略类。
2.所有策略类都需要暴露:策略类需要对外公开,以便可以被选择和使用。

结构

1.环境(Context):维护一个对策略对象的引用,负责将客户端请求委派给具体的策略对象执行。环境类可以通过依赖注入、简单工厂等方式来获取具体策略对象。
2.抽象策略(Abstract Strategy):定义了策略对象的公共接口或抽象类,规定了具体策略类必须实现的方法。
3.具体策略(Concrete Strategy):实现了抽象策略定义的接口或抽象类,包含了具体的算法实现。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
interface Strategy {
doOperation(num1: number, num2: number): number;
}

// 2. 实现策略接口
class OperationAdd implements Strategy {
doOperation(num1: number, num2: number): number {
return num1 + num2;
}
}

class OperationSubstract implements Strategy {
doOperation(num1: number, num2: number): number {
return num1 - num2;
}
}

class OperationMultiply implements Strategy {
doOperation(num1: number, num2: number): number {
return num1 * num2;
}
}

// 3. 创建context
class Context {
private strategy: Strategy;

constructor(strategy: Strategy) {
this.strategy = strategy;
}

executeStrategy(num1: number, num2: number): number {
return this.strategy.doOperation(num1, num2);
}
}

const context = new Context(new OperationAdd());
console.log('10 + 5 = ', context.executeStrategy(10, 5));

context.strategy = new OperationSubstract();
console.log('10 - 5 = ', context.executeStrategy(10, 5));

context.strategy = new OperationMultiply();
console.log('10 * 5 = ', context.executeStrategy(10, 5));

模板模式

概念

在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这种类型的设计模式属于行为型模式。

意图

在父类中定义了算法的骨架,并允许子类在不改变算法结构的前提下重定义算法的某些特定步骤。

何时使用

当存在一些通用的方法,可以在多个子类中共用时。

如何解决

1.定义抽象父类:包含模板方法和一些抽象方法或具体方法。
2.实现子类:继承抽象父类并实现抽象方法,不改变算法结构。

关键

1.模板方法:在抽象父类中定义,调用抽象方法和具体方法。
2.抽象方法:由子类实现,代表算法的可变部分。
3.具体方法:在抽象父类中实现,代表算法的不变部分。

优点与缺点

优点

1.封装不变部分:算法的不变部分被封装在父类中。
2.扩展可变部分:子类可以扩展或修改算法的可变部分。
3.提取公共代码:减少代码重复,便于维护。

缺点

类数目增加:每个不同的实现都需要一个子类,可能导致系统庞大。

结构

1.抽象父类(Abstract Class):
定义了模板方法和一些抽象方法或具体方法。
2.具体子类(Concrete Classes):
继承自抽象父类,并实现抽象方法。
3.钩子方法(Hook Method)(可选):
在抽象父类中定义,可以被子类重写,以影响模板方法的行为。
4.客户端(Client)(可选):
使用抽象父类和具体子类,无需关心模板方法的细节。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 父类
abstract class AbstractClass {
// 模板方法
templateMethod() {
this.operation1();
this.operation2();
}

// 具体方法
operation1() {
console.log('AbstractClass.operation1');
}

// 抽象方法
abstract operation2(): void;
}

// 子类1
class ConcreteClass1 extends AbstractClass {
operation2() {
console.log('ConcreteClass1.operation2');
}
}

// 子类2
class ConcreteClass2 extends AbstractClass {
operation2() {
console.log('ConcreteClass2.operation2');
}
}

// 客户端代码
const concreteClass1 = new ConcreteClass1();
concreteClass1.templateMethod();

const concreteClass2 = new ConcreteClass2();
concreteClass2.templateMethod();

访问者模式

概念

在访问者模式(Visitor Pattern)中,我们使用了一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变。这种类型的设计模式属于行为型模式。根据模式,元素对象已接受访问者对象,这样访问者对象就可以处理元素对象上的操作。

意图

旨在将数据结构与在该数据结构上执行的操作分离,从而使得添加新的操作变得更容易,而无需修改数据结构本身。

何时使用

当需要对一个对象结构中的对象执行多种不同的且不相关的操作时,尤其是这些操作需要避免”污染”对象类本身。

如何解决

1.定义访问者接口:声明一系列访问方法,一个访问方法对应数据结构中的一个元素类。
2.创建具体访问者:实现访问者接口,为每个访问方法提供具体实现。
3.定义元素接口:声明一个接受访问者的方法。
4.创建具体元素:实现元素接口,每个具体元素类对应数据结构中的一个具体对象。

关键

1.访问者接口:包含访问不同元素的方法。
2.具体访问者:实现了访问者接口,包含对每个元素类的访问逻辑。
3.元素接口:包含一个接受访问者的方法。
4.具体元素:实现了元素接口,提供给访问者访问的入口。

优点与缺点

优点

1.单一职责原则:访问者模式符合单一职责原则,每个类只负责一项职责。
2.扩展性:容易为数据结构添加新的操作。
3.灵活性:访问者可以独立于数据结构变化。

缺点

1.违反迪米特原则:元素需要向访问者公开其内部信息。
2.元素类难以变更:元素类需要维持与访问者的兼容。
3.依赖具体类:访问者模式依赖于具体类而不是接口,违反了依赖倒置原则。

结构

1.访问者(Visitor):
定义了访问元素的接口。
2.具体访问者(Concrete Visitor):
实现访问者接口,提供对每个具体元素类的访问和相应操作。
3.元素(Element):
定义了一个接受访问者的方法。
4.具体元素(Concrete Element):
实现元素接口,提供一个accept方法,允许访问者访问并操作。
5.对象结构(Object Structure)(可选):
定义了如何组装具体元素,如一个组合类。
6.客户端(Client)(可选):
使用访问者模式对对象结构进行操作。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 1. 定义接口
interface IVisitor {
visit(e: Element): void;
}

// 2. 定义具体访问者
class ConcreteVisitor implements IVisitor {
visit(e: Element): void {
console.log("具体访问者访问" + e.operation());
}
}

// 3. 定义元素
interface Element {
operation(): string;
}

// 4. 定义具体元素
class ConcreteElementA implements Element {
operation(): string {
return "具体元素A的操作。";
}
}

class ConcreteElementB implements Element {
operation(): string {
return "具体元素B的操作。";
}
}

// 5. 定义对象结构
class ObjectStructure {
private list: Element[] = [];
add(e: Element): void {
this.list.push(e);
}
remove(e: Element): void {
this.list = this.list.filter((item) => item !== e);
}
accept(visitor: IVisitor): void {
this.list.forEach((item) => {
item.operation();
});
}
}

// 6. 客户端代码
const os = new ObjectStructure();
os.add(new ConcreteElementA());
os.add(new ConcreteElementB());

const visitor = new ConcreteVisitor();
os.accept(visitor);

结语

本篇文章就分享到这里了,更多内容敬请期待,债见~

上一篇:
你真的了解防抖节流吗?
下一篇:
【回顾学习】-CSS选择器