Skip to content

Commit 27f3292

Browse files
committed
docs: update structual design pattern
1 parent 5d9cc2a commit 27f3292

7 files changed

Lines changed: 474 additions & 333 deletions

File tree

docs/design-patterns/structual/adapter.md

Lines changed: 111 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,42 +6,132 @@ group:
66
title: 结构型
77
order: 3
88
title: 适配器模式
9-
order: 2
9+
order: 1
1010
---
1111

1212
# 适配器模式
1313

14-
**适配器模式(Adapter Pattern)** 是将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式
14+
适配器模式(Adapter Pattern)是一种结构型设计模式,它允许将一个类的接口转换成客户端期望的另一个接口。适配器模式主要用于使原本由于接口不匹配而无法一起工作的两个类能够协同工作
1515

16-
### 模式结构
16+
适配器模式涉及以下几个关键角色:
1717

18-
适配器模式包含如下角色:
18+
1. **目标接口(Target Interface)**: 客户端所期望的接口,适配器通过实现这个接口,使得客户端可以调用适配器的方法。
19+
2. **适配器(Adapter)**: 实现目标接口并包装一个或多个被适配者对象,以便将调用转发给被适配者。
20+
3. **被适配者(Adaptee)**: 需要被适配的类或接口,它定义了客户端不知道的方法,适配器通过调用被适配者的方法来完成适配。
21+
4. **客户端(Client)**: 使用目标接口的对象,与适配器交互。
1922

20-
- Target(目标抽象类):目标抽象类定义客户所需接口,可以是一个抽象类或接口,也可以是具体类。
21-
- Adapter(适配器类):适配器可以调用另一个接口,作为一个转换器,对 Adaptee 和 Target 进行适配,适配器类是适配器模式的核心,在对象适配器中,它通过继承 Target 并关联一个 Adaptee 对象使二者产生联系。
22-
- Adaptee(适配者类):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码。
23-
- Client(客户类)
23+
适配器模式主要有两种形式:类适配器和对象适配器。
2424

25-
## 代码实现
25+
## 类适配器
2626

27-
```js
27+
在类适配器中,适配器类继承了目标接口,并且同时持有一个被适配者对象。通过继承目标接口,适配器可以将被适配者的方法暴露给客户端。
28+
29+
```typescript
30+
// 目标接口
31+
class Target {
32+
request() {}
33+
}
34+
35+
// 被适配者
36+
class Adaptee {
37+
specificRequest() {
38+
console.log("Adaptee's specific request");
39+
}
40+
}
41+
42+
// 类适配器
43+
class Adapter extends Target {
44+
constructor(adaptee) {
45+
super();
46+
this.adaptee = adaptee;
47+
}
48+
49+
request() {
50+
this.adaptee.specificRequest();
51+
}
52+
}
53+
54+
// 客户端
55+
const adaptee = new Adaptee();
56+
const adapter = new Adapter(adaptee);
57+
adapter.request(); // 输出:Adaptee's specific request
58+
```
59+
60+
## 对象适配器
61+
62+
在对象适配器中,适配器类持有一个被适配者对象的实例,并实现了目标接口。通过委托,适配器将客户端的请求传递给被适配者。
63+
64+
```typescript
65+
// 对象适配器
2866
class Adapter {
29-
test() {
30-
return '旧接口';
67+
constructor(adaptee) {
68+
this.adaptee = adaptee;
69+
}
70+
71+
request() {
72+
this.adaptee.specificRequest();
3173
}
3274
}
3375

34-
class Target {
35-
constructor() {
36-
this.adapter = new Adapter();
76+
// 客户端
77+
const adaptee = {
78+
specificRequest: function() {
79+
console.log("Adaptee's specific request");
3780
}
38-
test() {
39-
let info = this.adapter.test();
40-
return `适配${info}`;
81+
};
82+
83+
const adapter = new Adapter(adaptee);
84+
adapter.request(); // 输出:Adaptee's specific request
85+
```
86+
87+
适配器模式的使用场景包括:
88+
89+
- 当需要使用一个已经存在的类,而它的接口与你所需要的不匹配时。
90+
- 当你想创建一个可复用的类,该类可以与其他不相关的类或不可预见的类(即接口不一致的类)协同工作时。
91+
92+
适配器模式使得不同接口的类能够协同工作,使系统更加灵活,同时能够保持类的独立性和可复用性。
93+
94+
## 代码示例
95+
96+
假设我们有两个不同的日志库,一个是新日志库 NewLogger,一个是旧日志库 OldLogger,它们的接口不同。我们想要在应用程序中统一使用新日志库,但是现有的代码仍然使用旧日志库。这时候就可以使用适配器模式。
97+
98+
```typescript
99+
// 新日志库接口
100+
class NewLogger {
101+
log(message) {
102+
console.log(`New Logger: ${message}`);
41103
}
42104
}
43105

44-
const target = new Target();
45-
// '适配旧借口'
46-
console.log(target.test());
106+
// 旧日志库接口
107+
class OldLogger {
108+
logMessage(msg) {
109+
console.log(`Old Logger: ${msg}`);
110+
}
111+
}
112+
113+
// 适配器 - 将旧日志库适配成新日志库接口
114+
class OldLoggerAdapter extends NewLogger {
115+
constructor(oldLogger) {
116+
super();
117+
this.oldLogger = oldLogger;
118+
}
119+
120+
log(message) {
121+
// 调用旧日志库的方法
122+
this.oldLogger.logMessage(message);
123+
}
124+
}
125+
126+
// 客户端代码,使用新日志库接口
127+
const newLogger = new NewLogger();
128+
newLogger.log("This is a message from the new logger.");
129+
130+
// 适配器让旧日志库适应新日志库接口
131+
const oldLogger = new OldLogger();
132+
const adaptedLogger = new OldLoggerAdapter(oldLogger);
133+
adaptedLogger.log("This is a message from the old logger, adapted to the new logger interface.");
47134
```
135+
136+
在这个例子中,`OldLoggerAdapter` 充当适配器的角色,它继承了新日志库的接口,并持有一个旧日志库的实例。在适配器中,它的 `log` 方法调用了旧日志库的 `logMessage` 方法,从而实现了旧日志库适配到新日志库的接口。客户端代码使用新日志库接口,而适配器在内部负责将旧日志库的调用适配到新日志库的接口。
137+

docs/design-patterns/structual/bridge.md

Lines changed: 92 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,40 +6,110 @@ group:
66
title: 结构型
77
order: 3
88
title: 桥接模式
9-
order: 5
9+
order: 2
1010
---
1111

1212
# 桥接模式
1313

14-
桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式
14+
桥接模式(Bridge Pattern)是一种结构型设计模式,它将抽象部分与实现部分分离,使它们可以独立变化,而不影响彼此。桥接模式主要用于处理具有多层次继承结构的情况,通过将抽象部分和实现部分分开,使得系统更加灵活
1515

16-
### 模式结构
16+
在桥接模式中,有两个关键的角色:
1717

18-
桥接模式包含如下角色:
18+
1. **抽象类(Abstraction)**: 定义了抽象部分的接口,并维护一个指向实现部分的引用。
19+
2. **实现类(Implementor)**: 定义了实现部分的接口,被抽象类引用。
20+
3. **具体抽象类(Concrete Abstraction)**: 继承自抽象类,实现抽象类定义的接口。
21+
4. **具体实现类(Concrete Implementor)**: 继承自实现类,实现实现类定义的接口。
1922

20-
- Abstraction(抽象类):定义抽象接口,拥有一个 Implementor 类型的对象引用
21-
- RefinedAbstraction(扩充抽象类):扩展 Abstraction 中的接口定义
22-
- Implementor(实现类接口):是具体实现的接口,Implementor 和 RefinedAbstraction 接口并不一定完全一致,实际上这两个接口可以完全不一样 Implementor 提供具体操作方法,而 Abstraction 提供更高层次的调用
23-
- ConcreteImplementor(具体实现类):实现 Implementor 接口,给出具体实现
23+
以下是一个通俗易懂的 JavaScript 示例,假设我们要设计一个绘制不同形状的图形的系统:
2424

25-
### 模式分析
25+
```typescript
26+
// 实现类接口
27+
class DrawingAPI {
28+
drawCircle(x, y, radius) {}
29+
drawSquare(x, y, side) {}
30+
}
2631

27-
理解桥接模式,重点需要理解如何将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化。
32+
// 具体实现类 - SVG 绘制
33+
class SVGDrawingAPI extends DrawingAPI {
34+
drawCircle(x, y, radius) {
35+
console.log(`Drawing a circle at (${x},${y}) with radius ${radius} using SVG`);
36+
}
2837

29-
- 抽象化:抽象化就是忽略一些信息,把不同的实体当作同样的实体对待。在面向对象中,将对象的共同性质抽取出来形成类的过程即为抽象化的过程。
30-
- 实现化:针对抽象化给出的具体实现,就是实现化,抽象化与实现化是一对互逆的概念,实现化产生的对象比抽象化更具体,是对抽象化事物的进一步具体化的产物。
31-
- 脱耦:脱耦就是将抽象化和实现化之间的耦合解脱开,或者说是将它们之间的强关联改换成弱关联,将两个角色之间的继承关系改为关联关系。桥接模式中的所谓脱耦,就是指在一个软件系统的抽象化和实现化之间使用关联关系(组合或者聚合关系)而不是继承关系,从而使两者可以相对独立地变化,这就是桥接模式的用意。
38+
drawSquare(x, y, side) {
39+
console.log(`Drawing a square at (${x},${y}) with side ${side} using SVG`);
40+
}
41+
}
3242

33-
### 优点和缺点
43+
// 具体实现类 - Canvas 绘制
44+
class CanvasDrawingAPI extends DrawingAPI {
45+
drawCircle(x, y, radius) {
46+
console.log(`Drawing a circle at (${x},${y}) with radius ${radius} using Canvas`);
47+
}
3448

35-
桥接模式的优点:
49+
drawSquare(x, y, side) {
50+
console.log(`Drawing a square at (${x},${y}) with side ${side} using Canvas`);
51+
}
52+
}
3653

37-
- 分离抽象接口及其实现部分。
38-
- 桥接模式有时类似于多继承方案,但是多继承方案违背了类的单一职责原则(即一个类只有一个变化的原因),复用性比较差,而且多继承结构中类的个数非常庞大,桥接模式是比多继承方案更好的解决方法。
39-
- 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。
40-
- 实现细节对客户透明,可以对用户隐藏实现细节。
54+
// 抽象类
55+
class Shape {
56+
constructor(drawingAPI) {
57+
this.drawingAPI = drawingAPI;
58+
}
4159

42-
桥接模式的缺点:
60+
draw() {}
4361

44-
- 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
45-
- 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。
62+
resize() {}
63+
}
64+
65+
// 具体抽象类 - 圆
66+
class Circle extends Shape {
67+
constructor(x, y, radius, drawingAPI) {
68+
super(drawingAPI);
69+
this.x = x;
70+
this.y = y;
71+
this.radius = radius;
72+
}
73+
74+
draw() {
75+
this.drawingAPI.drawCircle(this.x, this.y, this.radius);
76+
}
77+
78+
resize(radius) {
79+
this.radius = radius;
80+
}
81+
}
82+
83+
// 具体抽象类 - 正方形
84+
class Square extends Shape {
85+
constructor(x, y, side, drawingAPI) {
86+
super(drawingAPI);
87+
this.x = x;
88+
this.y = y;
89+
this.side = side;
90+
}
91+
92+
draw() {
93+
this.drawingAPI.drawSquare(this.x, this.y, this.side);
94+
}
95+
96+
resize(side) {
97+
this.side = side;
98+
}
99+
}
100+
101+
// 客户端代码
102+
const svgDrawingAPI = new SVGDrawingAPI();
103+
const canvasDrawingAPI = new CanvasDrawingAPI();
104+
105+
const circle = new Circle(1, 2, 3, svgDrawingAPI);
106+
const square = new Square(4, 5, 6, canvasDrawingAPI);
107+
108+
circle.draw();
109+
square.draw();
110+
111+
circle.resize(4);
112+
circle.draw();
113+
```
114+
115+
在这个例子中,`DrawingAPI` 是实现类的接口,`SVGDrawingAPI``CanvasDrawingAPI` 是具体的实现类。`Shape` 是抽象类,`Circle``Square` 是具体的抽象类。通过这种设计,我们可以方便地切换不同的实现类,例如从 `SVG` 切换到 `Canvas` 绘制,而不需要修改 `Circle``Square` 类的代码。这样的设计使得抽象部分和实现部分可以独立演化,提高了系统的灵活性和可维护性。

docs/design-patterns/structual/composite.md

Lines changed: 76 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,88 @@ group:
66
title: 结构型
77
order: 3
88
title: 组合模式
9-
order: 6
9+
order: 3
1010
---
1111

1212
# 组合模式
1313

14-
**组合模式**(Composite)通常用于将对象组合成树形结构以表示“部分-整体的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性
14+
组合模式(Composite Pattern)是一种结构型设计模式,它允许你将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得客户端对单个对象和组合对象的使用具有一致性
1515

16-
**基本结构**
16+
在组合模式中,有两种关键角色:
1717

18-
组合模式主要有三种角色:
18+
1. **组件(Component)**: 定义了树形结构中所有具体对象和组合对象的共同接口。
19+
2. **叶子(Leaf)**: 实现了组件接口的叶子对象,它是树中的叶子节点,没有子节点。
20+
3.** 合成(Composite)**: 实现了组件接口的组合对象,它具有叶子和其他组合对象作为子节点,可以递归地组合成更复杂的树形结构。
1921

20-
- 抽象组件(Component):抽象类,主要定义了参与组合的对象的公共接口
21-
- 子对象(Leaf):组成组合对象的最基本对象
22-
- 组合对象(Composite):由子对象组合起来的复杂对象
22+
假设我们要构建一个文件系统的树形结构:
2323

24-
理解组合模式的关键是要理解组合模式对单个对象和组合对象使用的一致性。
24+
```typescript
25+
// 组件接口
26+
class FileSystemComponent {
27+
constructor(name) {
28+
this.name = name;
29+
}
30+
31+
// 公共接口方法
32+
display() {}
33+
}
34+
35+
// 叶子对象
36+
class File extends FileSystemComponent {
37+
display() {
38+
console.log(`File: ${this.name}`);
39+
}
40+
}
41+
42+
// 组合对象
43+
class Directory extends FileSystemComponent {
44+
constructor(name) {
45+
super(name);
46+
this.children = [];
47+
}
48+
49+
// 添加子节点
50+
add(component) {
51+
this.children.push(component);
52+
}
53+
54+
// 移除子节点
55+
remove(component) {
56+
const index = this.children.indexOf(component);
57+
if (index !== -1) {
58+
this.children.splice(index, 1);
59+
}
60+
}
61+
62+
display() {
63+
console.log(`Directory: ${this.name}`);
64+
this.children.forEach(child => {
65+
child.display();
66+
});
67+
}
68+
}
69+
70+
// 客户端代码
71+
const file1 = new File("document.txt");
72+
const file2 = new File("image.jpg");
73+
74+
const directory1 = new Directory("Documents");
75+
directory1.add(file1);
76+
directory1.add(file2);
77+
78+
const file3 = new File("video.mp4");
79+
80+
const directory2 = new Directory("Media");
81+
directory2.add(file3);
82+
83+
const rootDirectory = new Directory("Root");
84+
rootDirectory.add(directory1);
85+
rootDirectory.add(directory2);
86+
87+
// 显示整个文件系统结构
88+
rootDirectory.display();
89+
90+
```
91+
92+
在这个例子中,`FileSystemComponent` 是组件接口,`File` 是叶子对象,`Directory` 是组合对象。`Directory` 可以包含叶子对象(File)和其他组合对象(Directory),从而构建了一个树形结构。客户端代码可以通过调用 `display` 方法遍历整个文件系统结构,而不需要关心是文件还是目录,实现了对整体和部分的一致性访问。
2593

0 commit comments

Comments
 (0)