Skip to content

Commit 5d64c74

Browse files
committed
feat(依赖隔离模式): update article.md; update code
1 parent 9d84c47 commit 5d64c74

File tree

3 files changed

+59
-53
lines changed

3 files changed

+59
-53
lines changed

doc/TODO.org

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -321,11 +321,11 @@ finish TODO
321321
# * TODO 修改接口为type,《type》
322322

323323

324-
* TODO finish dependent pattern article
324+
* DONE finish dependent pattern article
325325

326-
** TODO draft
326+
** DONE draft
327327

328-
** TODO edit:统一格式; update 使用场景:推广; rename IRenderEngine to RenderEngine; replace Onion architecture image
328+
** DONE edit:统一格式; update 使用场景:推广; rename IRenderEngine to RenderEngine; replace Onion architecture image
329329

330330

331331

packages/依赖隔离模式/article.md

Lines changed: 53 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
## 需求
55

6-
编辑器使用Three.js引擎来创建一个场景
6+
编辑器使用Three.js引擎作为渲染引擎,来创建一个场景
77

88

99
## 实现思路
@@ -47,7 +47,7 @@ export let createScene = function () {
4747
}
4848
```
4949

50-
Editor在createScene函数中调用Three.js,创建场景
50+
createScene函数调用Three.js库创建场景
5151

5252

5353
## 提出问题
@@ -64,7 +64,6 @@ Editor在createScene函数中调用Three.js,创建场景
6464
## 概述解决方案?
6565

6666
编辑器改为引入Babylon.js库,并修改编辑器中与引擎相关的代码
67-
<!-- 直接将three.js引擎换成Babylon.js引擎,修改跟引擎相关的代码 -->
6867

6968
## 给出UML?
7069

@@ -77,8 +76,8 @@ Babylon.js是Babylon.js库
7776

7877
## 结合UML图,描述如何具体地解决问题?
7978

80-
将Three.js换成Babylon.js
81-
修改Editor的相关代码
79+
- 将Three.js换成Babylon.js
80+
- 修改Editor的相关代码
8281

8382

8483
## 给出代码?
@@ -104,9 +103,9 @@ Editor改为引入Babylon.js库,并修改createScene函数中与引擎相关
104103

105104
## 提出问题
106105

107-
替换引擎需要修改编辑器中所有相关代码,这样的成本太高了
106+
现在需要修改编辑器中所有与引擎相关代码,这样的成本太高了
108107

109-
有没有办法能在不修改编辑器相关代码的情况下实现替换引擎呢
108+
有没有办法能在不修改编辑器代码的情况下实现替换引擎呢
110109

111110

112111
# [给出使用模式的改进方案]
@@ -122,7 +121,7 @@ Editor改为引入Babylon.js库,并修改createScene函数中与引擎相关
122121
TODO tu
123122

124123

125-
RenderEngine接口是对引擎API的抽象
124+
RenderEngine接口是对渲染引擎的抽象
126125

127126
BabylonImplement是使用Babylon.js引擎对RenderEngine接口的实现
128127

@@ -134,7 +133,7 @@ Three.js是Three.js库
134133

135134
Client通过依赖注入的方式注入RenderEngine实现,使Editor能够调用它来创建场景
136135

137-
DependencyContainer是RenderEngine实现的容器,负责维护由Client注入的RenderEngine实现
136+
DependencyContainer是注入的RenderEngine实现的容器,提供它的get/set函数
138137

139138

140139
## 结合UML图,描述如何具体地解决问题?
@@ -216,7 +215,7 @@ export let setRenderEngine = (renderEngine: RenderEngine) {
216215
```
217216

218217

219-
DependencyContainer提供了get/set函数来获得和设置当前的RenderEngine实现
218+
DependencyContainer提供了get/set函数来获得和设置注入的RenderEngine实现
220219

221220

222221
Editor
@@ -237,7 +236,7 @@ export let createScene = function () {
237236
Editor增加了injectDependencies函数,实现了注入由Client传过来的RenderEngine实现
238237

239238
createScene函数通过DependencyContainer获得注入的RenderEngine实现,调用它来创建场景
240-
这里它只知道RenderEngine接口类型,没有依赖具体的RenderEngine实现
239+
它只知道RenderEngine接口,没有依赖具体的RenderEngine实现
241240

242241

243242
Client
@@ -269,13 +268,10 @@ Client注入了BabylonImplement
269268

270269
将外部依赖隔离后,系统变得更“纯”了,类似于函数式编程中的[“纯函数”](https://llh911001.gitbooks.io/mostly-adequate-guide-chinese/content/ch3.html#%E8%BF%BD%E6%B1%82%E2%80%9C%E7%BA%AF%E2%80%9D%E7%9A%84%E7%90%86%E7%94%B1)的概念,消除了外部依赖带来了副作用
271270

272-
那么哪些依赖属于外部依赖呢?对于编辑器而言,引擎、UI组件库(如Ant Design)、后端服务、文件操作、日志等都属于外部依赖;
271+
哪些依赖属于外部依赖呢?对于编辑器而言,引擎、UI组件库(如Ant Design)、后端服务、文件操作、日志等都属于外部依赖;
273272
对于引擎而言,各种子引擎(如物理引擎、动画引擎、例子引擎)、后端服务、文件操作等都属于外部依赖
274273

275-
<!-- 对于网站而言,UI组件库(如Ant Design)、后端服务、数据库操作等都属于外部依赖。 -->
276-
277-
<!-- 可以将每个外部依赖都抽象为对应的IDendency接口,从而都隔离出去。 -->
278-
可以将每个外部依赖都抽象为接口,从而都隔离出去。
274+
可以将每个可能会变化的外部依赖都抽象为接口,从而都隔离出去。
279275

280276

281277

@@ -290,24 +286,26 @@ TODO tu
290286

291287
我们来看看模式的相关角色:
292288

293-
- Dendepency
294-
该角色是依赖接口,对依赖的具体库的API进行了抽象
289+
- Dependency
290+
该角色是依赖的接口
295291
- DependencyImplement
296-
该角色是对Dendepency的实现
292+
该角色是对Dependency的实现
297293
- DependencyLibrary
298294
该角色是一个库
295+
- Client
296+
该角色是用户,通过依赖注入的方式注入DependencyImplement
299297
- DependencyContainer
300-
该角色是一个容器,负责维护由Client注入的DependencyImplement
298+
该角色是注入的DependencyImplement的容器,提供它的get/set函数
301299
- System
302-
该角色使用了一个或多个外部依赖,它只知道外部依赖的接口(Dendepency)而不知道外部依赖的具体实现(DendepencyImplement
300+
该角色使用了一个或多个外部依赖,它只知道外部依赖的接口(Dependency)而不知道具体实现(DependencyImplement
303301

304302

305303
## 角色之间的关系?
306304

307-
- 可以有多个Dendepency
305+
- 可以有多个Dependency
308306
如除了RenderEngine以外,还可以File、Server等
309-
- 一个Dendepency可以有多个DependencyImplement
310-
如对于RenderEngine,除了有ThreeImplement,还可以有BabylonImplement等
307+
- 一个Dependency可以有多个DependencyImplement
308+
如RenderEngine除了有ThreeImplement,还可以有BabylonImplement等
311309
- 一个DependencyImplement一般只使用一个DependencyLibrary,但也可以使用多个DependencyLibrary
312310
如对于RenderEngine,可以增加ThreeAndBabylonImplement,它同时使用Three.js和Babylon.js这两个DependencyLibrary。这样就使得编辑器可以同时使用两个引擎来渲染
313311

@@ -316,7 +314,7 @@ TODO tu
316314

317315
下面我们来看看各个角色的抽象代码:
318316

319-
- Dendepency的抽象代码
317+
- Dependency的抽象代码
320318
```ts
321319
type abstractType1 = any;
322320
...
@@ -379,6 +377,8 @@ export let doSomethingUseDependency1 = function () {
379377

380378
...
381379
}
380+
381+
更多doSomethingUseDependencyX函数...
382382
```
383383
- Client的抽象代码
384384
```ts
@@ -394,10 +394,10 @@ doSomethingUseDependency1()
394394

395395
依赖隔离模式主要遵循下面的设计原则:
396396
- 依赖倒置原则
397-
系统依赖于外部依赖的抽象(Dendepency)而不是外部依赖的细节(DependencyImplement和DependencyLibrary),从而外部依赖的细节的变化不会影响系统
397+
系统依赖于外部依赖的抽象(Dependency)而不是外部依赖的细节(DependencyImplement和DependencyLibrary),从而外部依赖的细节的变化不会影响系统
398398
- 开闭原则
399-
可以增加更多的Dendepency,从而隔离更多的外部依赖;或者对一个Dendepency增加更多的DependencyImplement,从而能够替换外部依赖的实现。这些都不会影响System,从而实现了对扩展开放
400-
如果需要升级外部依赖的版本,这也只会影响DependencyImplement和DependencyLibrary,不会影响System,从而实现了对修改关闭
399+
可以增加更多的Dependency,从而隔离更多的外部依赖;或者对一个Dependency增加更多的DependencyImplement,从而能够替换外部依赖的实现。这些都不会影响System,从而实现了对扩展开放
400+
如果需要修改已有的外部依赖(如升级版本),这也只会影响DependencyImplement和DependencyLibrary,不会影响System,从而实现了对修改关闭
401401

402402
依赖隔离模式也应用了“依赖注入”、“控制反转”的思想
403403

@@ -410,7 +410,7 @@ doSomethingUseDependency1()
410410
- 提高系统的稳定性
411411
外部依赖的变化不会影响系统
412412
- 提高系统的扩展性
413-
可以任意修改外部依赖的实现而不影响系统
413+
可以任意替换外部依赖而不影响系统
414414
- 提高系统的可维护性
415415
系统与外部依赖解耦,便于维护
416416

@@ -443,13 +443,13 @@ doSomethingUseDependency1()
443443

444444
## 注意事项
445445

446-
- Dendepency要足够抽象,才不至于在修改或增加DependencyImplement时需要修改Dendepency,从而影响System
447-
当然,在开发阶段难免考虑不足,如当一开始只有一个DependencyImplement时,Dendepency往往只会考虑这个DependencyImplement,导致在增加其它DependencyImplement时就需要修改Dendepency,使其更加抽象,这样才能容纳更多的DependencyImplement带来的变化
448-
因此,我们可以允许在开发阶段修改Dendepency,但是在发布前则确保Dendepency已经足够抽象和稳定,不需要再改动
446+
- Dependency要足够抽象,才不至于在修改或增加DependencyImplement时需要修改Dependency,从而影响System
447+
当然,在开发阶段难免考虑不足,如当一开始只有一个DependencyImplement时,Dependency往往只会考虑这个DependencyImplement,导致在增加其它DependencyImplement时就需要修改Dependency,使其更加抽象,这样才能容纳更多的DependencyImplement带来的变化
448+
因此,我们可以允许在开发阶段修改Dependency,但是在发布前则确保Dependency已经足够抽象和稳定
449449

450450
- 有多少个Dependency接口,DependencyContainer就有多少个get/set函数
451451

452-
- 最好一开始就使用依赖隔离模式将所有的可能会变化的外部依赖都隔离,这样可以避免到后期如果要修改外部依赖时需要修改系统所有相关代码的情况
452+
- 最好一开始就使用依赖隔离模式将所有的可能会变化的外部依赖都隔离,这样可以避免到后期修改外部依赖时需要修改系统所有相关代码的情况
453453

454454

455455
# 扩展
@@ -459,17 +459,20 @@ doSomethingUseDependency1()
459459

460460
如果基于依赖隔离模式这样设计一个架构:
461461

462-
- 定义4个层,其中的应用服务层、领域服务层、领域模型层为上下层的关系,上层依赖下层;外部依赖层则属于独立的层,层中的外部依赖是按照依赖隔离模式设计,在运行时注入
463-
- 将系统的所有外部依赖都隔离出去,也就是为每个外部依赖创建一个IDendepency,其中DependencyImplement位于外部依赖层,IDendepency位于领域模型层中的Application Core
464-
其它三层不依赖外部依赖层,而是依赖领域模型层中的Application Core(具体就是依赖IDendepency)
462+
- 定义4个层:外部依赖层、应用服务层、领域服务层、领域模型层,其中前者是后者的上层,上层依赖下层
463+
- 外部依赖层则属于独立的层,该层中的外部依赖是按照依赖隔离模式设计的,在运行时由用户注入
464+
- 将系统的所有外部依赖都隔离出去,也就是为每个外部依赖创建一个Dependency,其中DependencyImplement位于外部依赖层,Dependency位于领域模型层中的Application Core
465+
<!-- 其它三层不依赖外部依赖层,而是依赖领域模型层中的Application Core(具体就是依赖Dependency) -->
465466
- 运用[领域驱动设计DDD](https://www.cnblogs.com/chaogex/p/12408802.html)设计系统,将系统的核心逻辑建模为领域模型,放到领域模型层
466467

467468
那么这样的架构就是洋葱架构
468469
洋葱架构如下图所示:
469470
![image](https://img2022.cnblogs.com/blog/419321/202206/419321-20220609041114118-2037325753.webp)
470471

471-
它的核心思想就是将变化最频繁的外部依赖层隔离出去,并使变化最少的领域模型层独立而不依赖其它层。
472-
在传统的架构中,领域模型层会依赖外部依赖层(如在领域模型中调用后端服务等),但是现在却解耦了。这样的好处就是如果外部依赖层变化,不会影响其他层
472+
洋葱架构与传统的三层架构的区别是颠倒了层之间的依赖关系:洋葱架构将三层架构中的最下层(外部依赖层)改为最上层;将三层架构中的倒数第二层(领域模型层)下降为最下层
473+
洋葱架构的核心思想就是将变化最频繁的外部依赖隔离出去,并使变化最少的领域模型层独立而不依赖其它层。
474+
<!-- 在传统的架构中,领域模型层会依赖外部依赖层(如在领域模型中调用后端服务等),但是现在却解耦了 -->
475+
这样的好处是外部依赖层容易变化,但它的变化现在不会影响其他层
473476

474477

475478
<!-- # 结合其它模式
@@ -505,7 +508,7 @@ doSomethingUseDependency1()
505508
- 扩大使用场景
506509

507510
编辑器的外部依赖不只是引擎,也包括UI组件库等
508-
如需要将旧的UI组件库(如React UI 组件库-Antd)替换为新的组件库,则可按照依赖隔离模式,提出UI这个Dependency接口,并加入作为接口的实现的OldUIImplement、NewUIImplement,在这两个接口的实现中调用对应的UI组件库
511+
如需要将旧的UI组件库(如React UI 组件库-Antd)替换为新的组件库,则可按照依赖隔离模式,提出UI这个Dependency接口,并加入作为接口实现的OldUIImplement、NewUIImplement,在这两个接口的实现中调用对应的UI组件库
509512

510513

511514
除了编辑器外,引擎、网站等系统也可以使用依赖隔离模式
@@ -514,7 +517,7 @@ doSomethingUseDependency1()
514517

515518

516519

517-
有些外部依赖在运行时会变化,对于这种情况,在运行时注入对应的DependencyImplement即可
520+
有些外部依赖在运行时会变化,对于这种情况,使用依赖隔离模式后可以在运行时注入变化后的DependencyImplement
518521
如编辑器向用户提供了“切换渲染效果”的功能:用户点击一个按钮后,就可以切换渲染引擎来渲染场景
519522
为了实现该功能,只需在按钮的点击事件中注入对应的DependencyImplement到DependencyContainer中即可
520523

@@ -523,17 +526,20 @@ doSomethingUseDependency1()
523526
- 满足各种修改外部依赖的用户需求
524527

525528
我遇到过这种问题:3D应用开发完成后,交给3个外部用户使用。用了一段时间后,这3个用户提出了不同的修改外部依赖的要求:第一个用户想要升级3D应用依赖的渲染引擎A,第二个用户想要替换A为B,第三个用户想要同时使用B和升级后的A来渲染。
526-
如果3D应用是直接调用外部依赖库的话,我们就需要去修改交付的3份代码中系统的相关代码,且每份代码都需要不同的修改(因为3个用户的需求不同),工作量很大;
529+
如果3D应用是直接调用外部依赖库的话,我们就需要将交付的代码修改为3个版本,分别满足3个用户的需求
530+
每个版本都需要修改系统中与外部依赖相关的所有代码,这样导致工作量很大
531+
527532
如果使用了依赖隔离模式进行了解耦,那么就只需要对3D应用做下面的修改:
528533
1.修改AImplement和ALibrary(升级)
529534
2.增加BImplement
530535
3.增加BLibrary
531-
4.增加ABImplement
532-
对交付给用户的代码做下面的修改:
533-
1.更新第一个用户交付代码的AImplement和ALibrary
534-
2.为第二个用户交付代码增加BImplement、BLibrary;修改Client代码,注入BImplement
535-
3.为第三个用户交付代码增加ABImplement、BLibrary;修改Client代码,注入ABImplement
536-
相比之下工作量减少了很多
536+
537+
交付的代码只有1个版本,只是在Client中分别对这3个用户注入不同的DependencyImplement:
538+
1.Client为第一个用户注入AImplement
539+
2.Client为第二个用户注入BImplement
540+
2.Client为第三个用户注入ABImplement
541+
542+
这样就能减少很多工作量
537543

538544

539545

@@ -543,7 +549,5 @@ doSomethingUseDependency1()
543549

544550
关于洋葱架构,可以在网上搜索“洋葱架构”、“the-onion-architecture-part”
545551

546-
关于洋葱架构,可以在网上搜索“洋葱架构”、“the-onion-architecture-part”
547-
548552
六边形架构类似于洋葱架构,可以在网上搜索“六边形架构”
549553

依赖隔离模式代码/role_abstract/System.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@ export let doSomethingUseDependency1 = function () {
1212
let abstractType1 = abstractOperate1()
1313

1414
...
15-
}
15+
}
16+
17+
更多doSomethingUseDependencyX函数...

0 commit comments

Comments
 (0)