Skip to content

Commit 0dead03

Browse files
committed
feat(再看设计原则): update all articles
1 parent 8066f14 commit 0dead03

File tree

12 files changed

+96
-113
lines changed

12 files changed

+96
-113
lines changed

doc/TODO.org

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -329,14 +329,14 @@ finish TODO
329329

330330

331331

332-
* TODO 完成再看设计原则
332+
* DONE 完成再看设计原则
333333
每个设计原则给两个函数式编程的案例
334334

335335

336336
** DONE draft
337337

338338

339-
** TODO update
339+
** DONE update
340340

341341

342342

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
案例代码均使用函数式编程
22

3-
每个设计原则给两个函数式编程的案例
3+
<!-- 每个设计原则给两个函数式编程的案例 -->

packages/再看设计原则/依赖倒置原则/article.md

Lines changed: 16 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,22 @@
88
# 补充说明
99

1010
<!-- 如何分辨高层模块、低层模块? -->
11-
所谓的高层、低层模块是按照依赖的方向来定的,如模块1依赖模块2,则我们说模块1属于高层模块,模块2属于低层模块
11+
所谓的高层、低层模块是按照依赖的方向来定的,如模块1依赖模块2,我们就说相对而言,模块1属于高层模块,模块2属于低层模块
1212

1313

14-
<!-- 在代码中,抽象、细节在代码中指的是什么? -->
15-
模块间应该抽象依赖于抽象
16-
实现模块之间不应该发生直接的依赖关系,其依赖关系应该是通过接口产生的;
17-
接口不依赖于实现模块;
18-
实现模块依赖接口
14+
“抽象”在函数式编程的代码中指的就是接口或者类型
15+
“细节”在函数式编程的代码中指的就是模块或者函数
1916

20-
所以说,符合依赖倒置原则的编程可以看作是“面向接口编程”
17+
依赖倒置原则具体是指模块或者函数之间不应该直接依赖,而应该是依赖于它们的接口或者类型
18+
19+
所以说,符合依赖倒置原则的编程可以看作是“面向接口/类型编程”
2120

2221

2322
符合依赖倒置原则有下面的好处:
24-
- 减少模块之间的耦合性
23+
- 减少细节之间的耦合性
2524
- 便于替换细节
2625
- 提高系统的稳定性
27-
- 降低并行开发引起的风险
26+
- 降低并行开发细节时引起的风险
2827
- 提高代码的可读性和可维护性
2928

3029

@@ -34,7 +33,7 @@
3433
TODO tu
3534
上图是读者阅读技术书的领域模型
3635

37-
相关伪代码如下
36+
伪代码如下
3837
TechnicalBook
3938
```ts
4039
type technicalBook = {
@@ -60,11 +59,11 @@ Client
6059
Reader.read(TechnicalBook)
6160
```
6261

63-
如果增加小说书,读者既可以阅读技术书又可以阅读小说书,具体由Client决定让读者阅读哪本书
62+
如果增加小说书,让读者既可以阅读技术书又可以阅读小说书,这具体是由Client决定读者阅读哪类书
6463
修改后的领域模型如下:
6564
TODO tu
6665

67-
修改后的相关伪代码如下
66+
修改后的伪代码如下
6867
TechnicalBook代码不变
6968

7069
NovelBook
@@ -103,16 +102,16 @@ Client
103102
Reader.read(TechnicalBook, NovelBook, "novel")
104103
```
105104

106-
我们可以看到,每增加一本书,Reader都会受影响
107-
我们将其改为符合依赖倒置原则,从而使Reader不受影响
105+
因为Reader依赖了每类书,所以每增加一类书,Reader都需要对应修改代码
106+
我们将其改为符合依赖倒置原则,解除Reader与每类书的依赖
108107
修改后的领域模型如下:
109108
TODO tu
110109

111-
现在Reader改为依赖Book这个接口,而不再依赖它的具体实现模块了
110+
现在提出了书的接口Book,让Reader改为依赖接口,而不依赖它的实现模块
112111
这样做的好处是只要Book这个接口不变,它的实现模块的变化不会影响到Reader
113112

114113

115-
修改后的相关伪代码如下
114+
修改后的伪代码如下
116115
Book
117116
```ts
118117
export interface Book {
@@ -147,12 +146,4 @@ Client
147146
```ts
148147
//选择让读者读小说书
149148
Reader.read(NovelBook)
150-
```
151-
152-
<!--
153-
154-
155-
156-
157-
158-
# 案例2 -->
149+
```

packages/再看设计原则/单一职责原则/article.md

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
# 定义
22

3-
只有一个原因引起变化;
4-
一次只做一件事情
5-
6-
<!-- # 解释定义 -->
7-
3+
只有一个原因引起变化,或者说一次只做一件事情
84

95

106
# 补充说明
@@ -14,24 +10,24 @@
1410
从函数名上就能看出该函数是否符合单一职责原则,如getAndSetData这个函数名一看就知道做了两件事情,所以应该将其拆分为getData、setData这两个函数
1511

1612

17-
如果一个函数做了多件事情,有下面的缺点:
13+
如果一个模块或者函数做了多件事情,有下面的缺点:
1814

19-
- 任何一件事情的变化都会影响该函数
20-
- 用户调用该函数时,本能只预期它做某件事情,结果它实际上还做了其它的事情,这可能造成bug
15+
- 任何一件事情的变化都会影响它
16+
- 用户调用它时,本来只预期它做某件事情,结果它实际上还做了其它的事情,这可能造成不可预料的bug
2117

2218

2319
# 案例1
2420

2521
TODO tu
26-
上图是操作书的接口,它不符合单一职责原则
27-
这是因为它的getBookID、setBookID是书的数据的操作,而它的addBook是书的行为的操作
22+
上图是操作书的接口,它不符合单一职责原则
23+
这是因为它的getBookID、setBookID函数是对书的数据的操作,而它的addBook函数则是对书的行为的操作
2824

2925
应该将其拆分为两个接口,拆分后的接口如下图所示:
3026
TODO tu
3127

32-
BookData负责书的数据
33-
BookAction负责书的行为
34-
BookInfo实现BookData和BookAction
28+
BookData负责操作书的数据
29+
BookAction负责操作书的行为
30+
原来的BookInfo改为实现BookData和BookAction
3531

3632

3733
# 案例2
@@ -45,11 +41,10 @@ type phone = number
4541

4642
type changeUser = (newUserData: [userName, phone]) => void
4743
```
48-
上面是一个函数的签名
49-
从签名可知,该函数不符合单一职责原则
50-
这是因为它的形参是一个数组,它包含了多个修改用户的的值,这说明该函数做了多件事情
44+
上面代码是一个函数的签名
45+
从签名可知,该函数不符合单一职责原则,这是因为它的形参是一个元组,包含了多个新的用户值,这说明该函数做了多件事情
5146
52-
应该将其拆分为多个函数,拆分后的函数如下所示
47+
应该将其拆分为多个函数,拆分后的函数签名如下所示
5348
```ts
5449
type userName = string
5550

packages/再看设计原则/合成复用原则/article.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,49 +5,51 @@
55
# 补充说明
66

77
“继承”属于面向对象编程,函数式编程中没有“继承”的概念
8-
但是,函数式编程其实也可以实现“继承”,只是把“类”换成“模块”,如Rescript支持在一个模块B中引入另一个模块A,使得B获得了A的所有成员,这相当于B继承了A
8+
但是,函数式编程其实也可以实现“继承”,只需要把“类”换成“模块”即可。
9+
如Rescript作为一个函数式编程语言,支持在一个模块B中引入另一个模块A,使B获得A所有的成员,这就相当于实现了B继承A
910

1011
相关代码如下:
1112
```res
1213
module A = {
1314
let value1 = "A1"
1415
1516
let func1 = () => {
17+
//返回1
1618
1
1719
}
1820
}
1921
2022
module B = {
21-
//引入A
23+
//引入A,获得了A.value1, A.func1
2224
include A
2325
2426
//覆盖了A.value1
2527
let value1 = "B1"
2628
2729
let func2 = () => {
28-
//调用A.func1
30+
//调用A.func1并返回
2931
func1()
3032
}
3133
}
3234
```
3335

34-
所以说合成复用原则也适用于函数式编程
36+
所以说合成复用原则也适合函数式编程
3537

36-
继承的领域模型如下
38+
继承在函数式编程中的领域模型如下
3739
TODO tu
3840

3941
我们来看下继承的优缺点:
4042
继承的优点:
4143
- 实现新的子模块比较容易,因为子模块可以直接通过继承获得父模块的成员
42-
- 可以在父模块的基础上扩展子模块
44+
<!-- - 可以在父模块的基础上扩展子模块 -->
4345

4446
继承的缺点:
4547

4648
- 继承破坏了封装,父模块的细节完全暴露给子模块,父模块的任何细节发生了改变都会影响到子模块
4749
- 继承是静态的,在运行时不能改变继承关系
4850

4951

50-
组合的领域模型如下
52+
组合在函数式编程中的领域模型如下
5153
TODO tu
5254

5355
我们来看下组合的优缺点:
@@ -74,5 +76,5 @@ TODO tu
7476
因此,我们将它们由继承关系改为组合关系,修改后的领域模型如下:
7577
TODO tu
7678

77-
现在,一个人可以通过组合不同的角色,来拥有多个角色
79+
现在,一个人可以通过组合不同的角色,来拥有多个角色,所以人与角色是“Has-A”的关系
7880

packages/再看设计原则/开闭原则/article.md

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,26 @@
55

66
# 补充说明
77

8-
一个软件实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化
8+
系统应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化
99

1010

11-
<!-- # 做项目的时候哪些地方违反了开闭原则? -->
11+
<!-- # 项目中哪些地方违反了开闭原则? -->
12+
在项目中,如果我们为了增加第三方库的功能而直接修改第三方库的代码,那么这就违反了开闭原则,因为这是通过修改了已有代码来实现变化
1213

13-
具体来说,如果我们为了增加第三方库的功能而直接修改第三方库的代码,那么这就违反了开闭原则
1414

15-
**为什么违反**
16-
这是通过修改了已有代码来实现变化
15+
<!-- **造成什么问题** -->
16+
这会造成下面的问题:
17+
升级第三方库的版本时,会导致原有的修改失效,从而不得不再修改一遍
1718

18-
**造成什么问题**
19-
升级第三方库的版本时,会导致原有的修改失效,从而不得不再次修改一遍
2019

21-
22-
**如何改进**
23-
24-
有很多改进的方法,其中一个方法就是使用设计模式中的“中间者模式”,增加一个模块A来封装第三方库,让用户改为调用模块A而不是直接调用第三方库。
25-
这样在需要增加第三方库的功能时,则只增加一个模块B,使其继承模块A,然后在模块B中加入增加的功能代码,让用户改为调用模块B
20+
<!-- **如何改进** -->
21+
有很多改进的方法,其中一个方法就是使用设计模式中的“中间者模式”,增加一个模块A来封装第三方库,让用户改为调用模块A而不是直接调用第三方库
22+
这样在需要增加第三方库的功能时,只需增加一个模块B,使其继承模块A,然后在模块B中加入增加的功能代码,并让用户改为调用模块B
2623

2724
这样做的话就通过扩展而不是修改来实现了变化,从而符合开闭原则
2825

26+
值得注意的是:因为开闭原则允许修改用户的代码,所以这里允许用户改为调用模块B
27+
2928

3029
# 案例1
3130

@@ -46,21 +45,20 @@ TODO tu
4645

4746
上面三种实现思路都违反了开闭原则,因为它们都是通过修改Cloth的相关代码来实现变化
4847

49-
我们可以增加一个继承TShirt的子模块:OffTShirt,在其中增加getOffPrice函数
48+
我们可以增加一个继承TShirt的子模块:OffTShirt,在其中覆写getPrice函数,实现打折处理
5049
领域模型如下:
5150
TODO tu
5251

53-
这样就只需要修改用户模块ClothStore,使其从调用TShirt的getPrice函数改为调用OffTShirt的getOffPrice函数,而不需要修改Cloth的相关代码
52+
这样就只需要修改用户模块ClothStore,使其从调用TShirt改为调用OffTShirt,而不需要修改Cloth的相关代码
5453

5554
这样就通过扩展来实现了变化,符合开闭原则
5655

57-
值得注意的是:开闭原则允许修改用户模块
58-
5956

6057
# 案例2
6158

6259

63-
我们来看下另一个例子,我们要实现一个人员管理系统,领域模型如下:
60+
我们来看另一个例子
61+
我们要实现一个人员管理系统,领域模型如下:
6462
TODO tu
6563

6664
人员固定有三个数据:姓名、卡号、部门
@@ -72,7 +70,7 @@ TODO tu
7270
我们修改领域模型,修改后如下:
7371
TODO tu
7472

75-
这里将人员和它的数据改为了组合关系
73+
这里将人员和它的数据改为组合关系
7674
比如对于原来的三个数据,现在它们的关系如下:
7775
```ts
7876
人员 = 人员数据项1 + 人员数据项2 + 人员数据项3
@@ -83,8 +81,9 @@ TODO tu
8381
人员数据项3 = 名称(部门) ++ 类型(string)
8482
```
8583

86-
可以通过增加一个人员数据项,从而实现增加一个人员的数据;
87-
可以通过增加一个修改后的人员数据项,并使人员从组合修改前的数据项改为组合修改后的数据项,从而实现修改人员的数据;
88-
可以通过使人员不再组合某个数据项,从而实现删除人员的数据
84+
现在可以通过这样来实现用户的需求:
85+
通过增加一个人员数据项,从而实现增加人员的一个数据;
86+
通过增加一个修改后的人员数据项,并使人员组合修改后的数据项,从而实现修改人员的一个数据;
87+
通过使人员不再组合某个数据项,从而实现删除人员的某个数据
8988

90-
这些都是通过扩展而非修改来实现变化,所以符合开闭原则
89+
因为这些都是通过扩展而非修改来实现变化,所以符合开闭原则
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<mxfile host="Electron" modified="2023-04-17T23:14:55.258Z" agent="5.0 (Macintosh; Intel Mac OS X 11_4_0) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/14.6.13 Chrome/89.0.4389.128 Electron/12.0.7 Safari/537.36" etag="q97hS0YzKS2D-LDZKWQ_" version="14.6.13" type="device"><diagram id="Bfc0rugBsH569nbIfP42" name="第 1 页">7VhNc6M4FPw1OmbKgDFwNJhkqsap3dpkZ2aPCsigRCBWyMHOr98nkAwYx5uDk5lDqqiyXuvpq7sFL0FOVOxuBK7yW54ShuxZukPOCtm25QYz+FHIvkNc3++ATNBUJ/XAHX0hGtTjsi1NST1KlJwzSasxmPCyJIkcYVgI3ozTNpyNV61wRibAXYLZFP1BU5l3qG97Pf6V0Cw3K1uLoOspsEnWJ6lznPJmADkxciLBuexaxS4iTJFneOnGXb/Se9iYIKV8y4DqJvm+WScPfBlh9tLcP93Gj1d6s8+YbfWBkb1gMF+44TAt7FruNRWLf7fcdFzVrVBLSHDsatd3Qitrf2MPhQ4Ku0aIgmszL2ywm1ondtwcVrGBpko1twW7FriAZtjkVJK7CicKb8BngOWyYBBZqlvrojwDHUaN+UxvVpvKsQ/LDUnTPD4TIcluAGkSbwgviBR7SDG9My2odrTl6bjp/eHMNZYPvWHuAtaezA5z97JBQyt3WkUZB38n93+Uj03zUH57Ce6fZHJlTVWM5yhcqid2UWChwJ8S3dCC4ZIYlnSPYi3JKUvXeM+3auu1xMmTicKcC/oC+djQD93CcGwv1GyUsYgzLgAoebtAP+hOTaaXEUTZ6E8jhnUE3eLdKHGNa2k2yBnDVU0f2i2rgQUWGS1DLiUvdNIFtLaDsdaH19JAa+uk1rP5O2ltn7ixcNSZEnrpocBpFZ8hf9Xdz1oKWmYT9YEA2Yon+BM5UuuEgJjRrISQkY0aphik8JJcalhydSdruKKw1rrNWc175C9zJQHiMHbD2hdhTtOUlEpfLrHEDwf/VZyWsiXODeEBfqPZFxe5sPEIYquP4VHpQka8hLNg2kpNwCkNqeVJE5y9Qf/vDOOExduMYPIu7gPnjA9A+6XVNq5R6H364B194Nq/2AfzV30QIN9BS181Ak81Pn3wfj7w/I/zwclKbjGRlKRQyeoQGMl5xkvM4h4FnrdlSlLNcp+z5kq/9rv6SKTc66873ko+Lr1IaUp2a3FUCrTAq5/gmm9FQs4cx9W1PnzWyRuEUmc9K5MgDEv6PK7qLy6C+5ZCDCriZdy+oL32Qz1VbsDwoIC1/AsVsM5RUeN4U+9aJ7zrXqB+PUnbtDj9EO/C1n/q8W3wjwrgPaLD1W7YudofovfyvDf1/DmX/Sae9yaejxhVZ/9YV199oKsh7P9ub/sG//1w4v8A</diagram></mxfile>
1+
<mxfile host="Electron" modified="2023-04-18T03:32:21.979Z" agent="5.0 (Macintosh; Intel Mac OS X 11_4_0) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/14.6.13 Chrome/89.0.4389.128 Electron/12.0.7 Safari/537.36" etag="rtDaz4FCV3n8fYhQhi7F" version="14.6.13" type="device"><diagram id="Bfc0rugBsH569nbIfP42" name="第 1 页">7Vhdb5swFP01fuwUIAnhMRDaSUu1ae2+Hl1wwI3BzDgl6a/ftTEBSppFWpPtYRJSfI+vsX3OMVyCnCDb3ghcpLc8JgzZo3iLnAWybcsde/CjkF2NuNaoBhJBY5PUAnf0mRiwSdvQmJS9RMk5k7TogxHPcxLJHoaF4FU/bcVZf9YCJ2QA3EWYDdFvNJZpjc5st8XfE5qkzczW1Gw4w02y2UmZ4phXHcgJkRMIzmXdyrYBYYq8hpd63PUrvfuFCZLLUwYUN9HX1TJ64PMAs+fqfn0bPl6ZxT5htjEbRvaUwf38FYfbwqrlzlAx/bnhTcdVqYWaQ4JjF9u2E1qJ/g1d5DvIrxs+8q6b+8IC61ubxJqb/Sw20FSo5iZj1wJn0PSrlEpyV+BI4RX4DLBUZgwiS3UbXZRnoKNRYzwyizWmcuz9dF3SDI9PREiy7UCGxBvCMyLFDlKa3pERdNd43MRV6w9nbLC06w3PgNh4Mtnfu5UNGka5wyrK0PsS3X/MH6vqIf/w7N2vZXRlDVUMx8ifqyucIM9C3mxIdEUzhnPSsGR6FGtRSlm8xDu+UUsvJY7WTeSnXNBnyMcN/dAt9hxrziljAWdcAJBzPUE76E7dzEwjiLLRp0YM6wV0i7e9xCUuZbNAzhguSvqgl6wGZlgkNPe5lDwzSR3trdnbaG97fe33j6mO9tZB7a1zaW8fOMGw1ZESfu4iz9EOGKHZoj6vpRQ0TwZuAAKkFlPwNXmh3gFBMaNJDiEjKzVMMUjhoTk3sOTqjJZwZGGupc5ZjFvkc3NEAeIwdsX0gzGlcUxypTeXWOKHvR8LTnOpiZv4cAGVwejdBE1g4QHEVhvDpdKFDHgOe8FUS03AORUp5UmmOHrCfu8U4wznRGPY0zP5wjniC/DC3NKNa+S7/31xQV9Mpn/ZF+NXfeGhmYPmM9XwXNX474vL+WJmn+iL2Z/74mAlOB1ITGKohE0IDKU84TlmYYsC75s8JrFhvc1ZcqWnfi8/Eil3hkC8kbxfupG8Kfmt6YtSQgOvvrJLvhERObKdiflWgLKAnCCU2utRmQRhWNKn/lfBm4swOaWQg4p6HuoHuKtf7EPlOgyfoQiynBdF0PjAu8464N3JG9RAB2kbFrcX8S4s/bsZr4MfKoDnigkX227nYrePzuV5d+j5Yy77RzzvDjwfMKr2fllXX13Q1RC23/26r/PviRP+Ag==</diagram></mxfile>

packages/再看设计原则/接口隔离原则/article.md

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
# 定义
22

3-
用户不应该依赖它不需要的接口
43
模块间的依赖关系应该建立在最小的接口上
54

65

76
# 补充说明
87

9-
接口尽量细化,同时接口中的方法尽量少
8+
应该将庞大的接口拆分成更小的接口
109

1110
“接口”属于面向对象编程,它在函数式编程中的对应是“类型”
1211

@@ -26,19 +25,17 @@ export type type1 = {
2625
}
2726
```
2827
29-
两者是等价的
28+
这两者是等价的,所以说接口隔离原则同样适合函数式编程,具体是指类型应该保持最小
3029
3130
值得注意的是:
3231
类型名首写字母是小写,而接口名首写字母是大写
3332
34-
接口隔离原则同样适合函数式编程,也就是指类型应该保持最小
35-
3633
3734
3835
3936
# 案例1
4037
41-
我们开发了一个用户管理系统,其中有一个查询接口,方便管理员查询拥有,领域模型如下:
38+
我们开发了一个用户管理系统,其中有一个查询接口,方便管理员查询用户,领域模型如下:
4239
TODO tu
4340
4441
系统上线后,发现系统速度非常慢,这是由于查询接口中的complexSerach函数性能太差造成的

0 commit comments

Comments
 (0)