1
-
2
-
3
1
# 编辑器使用引擎创建场景
4
2
5
3
## 需求
13
11
14
12
15
13
## 给出UML
14
+ ** 领域模型**
16
15
17
16
18
17
![ 领域模型图] ( ./story_before/UML.png )
19
18
20
- 总体来看,分为用户 、编辑器、引擎这三个部分
19
+ 总体来看,领域模型分为用户 、编辑器、引擎这三个部分
21
20
22
21
我们看下用户这个部分:
22
+
23
23
Client是用户
24
24
25
25
我们看下编辑器这个部分:
26
+
26
27
Editor是编辑器
27
28
28
29
我们看下引擎这个部分:
30
+
29
31
Three.js是Three.js引擎
30
32
31
33
@@ -80,6 +82,7 @@ Editor引入Three.js引擎,并在createScene函数中调用它来创建场景
80
82
- Editor改为引入Babylon.js引擎,并修改Editor中与引擎相关的代码
81
83
82
84
## 给出UML
85
+ ** 领域模型**
83
86
84
87
85
88
![ 领域模型图] ( ./story_after/UML.png )
@@ -101,7 +104,8 @@ Editor引入Three.js引擎,并在createScene函数中调用它来创建场景
101
104
102
105
Client代码跟之前一样,故省略
103
106
104
- 我们看下Editor的代码:
107
+ 我们看下Editor的代码
108
+
105
109
### Editor的代码
106
110
107
111
Editor
@@ -123,25 +127,26 @@ Editor改为引入Babylon.js引擎,并修改createScene函数中与引擎相
123
127
124
128
## 提出问题
125
129
126
- - 替换引擎的成本太高
130
+ - 替换引擎的成本太高
127
131
替换引擎需要修改Editor中所有与引擎相关代码,成本太高了。有没有办法能在不修改Editor代码的情况下实现替换引擎呢?
128
132
129
133
130
134
# 使用依赖隔离模式来改进
131
135
132
136
## 概述解决方案
133
137
134
- - 解除依赖
138
+ - 解除依赖
135
139
只要解除Editor和引擎的依赖,把替换引擎的逻辑隔离出去就可以实现
136
140
137
141
138
142
139
143
## 给出UML
140
144
145
+ ** 领域模型**
141
146
142
147
![ 领域模型图] ( ./story_improve/UML.png )
143
148
144
- 总体来看,分为用户 、编辑器、引擎、容器这四个部分
149
+ 总体来看,领域模型分为用户 、编辑器、引擎、容器这四个部分
145
150
146
151
我们看下用户这个部分:
147
152
@@ -171,14 +176,15 @@ Three.js是Three.js引擎
171
176
172
177
DependencyContainer是保存注入的Engine接口实现的容器,提供操作它的get和set函数
173
178
174
- ** 依赖关系**
179
+ <!-- **依赖关系** -->
180
+ 我们来看下依赖关系:
175
181
176
182
Client通过依赖注入的方式注入Engine接口的一个实现(BabylonImplement或者ThreeImplement),从而使Editor能够调用它来创建场景
177
183
178
184
179
185
## 结合UML图,描述如何具体地解决问题
180
186
181
- - 替换Three.js为Babylon.js引擎现在不再影响Editor了,只需要增加BabylonImplement,并让Client从注入ThreeImplement改为注入BabylonImplement即可
187
+ - 替换Three.js为Babylon.js引擎现在不再影响Editor了,只需要增加BabylonImplement,并让Client从注入ThreeImplement改为注入BabylonImplement即可
182
188
因为Editor只依赖Engine接口,所以Engine接口的实现的变化不会影响Editor
183
189
184
190
@@ -226,7 +232,7 @@ Editor增加了injectDependencies函数,它将Client注入的Engine接口实
226
232
227
233
createScene函数通过DependencyContainer获得注入的Engine接口实现,它的类型是Engine接口,用来创建场景
228
234
229
- 值得注意的是:
235
+ 值得注意的是:
230
236
Editor只依赖Engine接口,没有依赖Engine接口的实现
231
237
232
238
@@ -337,61 +343,62 @@ BabylonImplement的implement函数使用了Babylon.js引擎,返回了Engine接
337
343
338
344
## 通用UML
339
345
346
+ ** 领域模型**
340
347
341
348
![ 领域模型图] ( ./role_abstract/UML.png )
342
349
343
350
344
351
<!-- ## 分析角色 -->
345
352
346
- 我们来看看模式的相关角色:
353
+ <!-- 我们来看看模式的相关角色: -->
347
354
348
- 总体来看,分为用户 、系统、外部依赖、容器这四个部分
355
+ 总体来看,领域模型分为用户 、系统、外部依赖、容器这四个部分
349
356
350
357
351
358
我们看下用户这个部分:
352
359
353
- - Client
360
+ - Client
354
361
该角色是用户,通过依赖注入的方式注入DependencyImplement
355
362
356
363
我们看下系统这个部分:
357
364
358
- - System
365
+ - System
359
366
该角色使用了外部依赖,它只知道外部依赖的接口(Dependency)而不知道具体实现(DependencyImplement)
360
367
361
368
362
369
我们看下外部依赖这个部分:
363
370
364
- - Dependency
371
+ - Dependency
365
372
该角色是外部依赖的接口
366
- - DependencyImplement
373
+ - DependencyImplement
367
374
该角色是Dependency的实现
368
- - DependencyLibrary
375
+ - DependencyLibrary
369
376
该角色是一个库
370
377
371
378
372
379
我们看下容器这个部分:
373
380
374
- - DependencyContainer
381
+ - DependencyContainer
375
382
该角色是保存注入的DependencyImplement的容器,提供操作它的get和set函数
376
383
377
384
378
- ## 角色之间的关系
385
+ ** 角色之间的关系**
379
386
380
- - 可以有多个Dependency
387
+ - 可以有多个Dependency
381
388
如除了Engine以外,还可以有File、Server等Dependency,其中每个Dependency对应一个外部依赖
382
389
383
- - 一个Dependency可以有多个DependencyImplement来实现
390
+ - 一个Dependency可以有多个DependencyImplement来实现
384
391
如Engine的实现除了有ThreeImplement,还可以有BabylonImplement等实现
385
392
386
393
387
394
- Client可以依赖注入多个Dependency接口的实现。其中,对于一个Dependency接口而言,Client只依赖注入实现它的一个DependencyImplement
388
395
389
396
- 因为System可以使用多个Dependency接口,所以它们是一对多的关系
390
397
391
- - 一个DependencyImplement一般只使用一个DependencyLibrary,但也可以使用多个DependencyLibrary
398
+ - 一个DependencyImplement一般只使用一个DependencyLibrary,但也可以使用多个DependencyLibrary
392
399
如可以增加实现Engine接口的ThreeAndBabylonImplement,它同时使用Three.js和Babylon.js这两个DependencyLibrary来创建场景
393
400
394
- - 只有一个DependencyContainer容器,它保存了所有注入的DependencyImplement,为每个DependencyImplement都提供了get和set函数
401
+ - 只有一个DependencyContainer容器,它保存了所有注入的DependencyImplement,为每个DependencyImplement都提供了get和set函数
395
402
396
403
397
404
@@ -500,9 +507,9 @@ export let api1 = function () {
500
507
501
508
依赖隔离模式主要遵循下面的设计原则:
502
509
503
- - 依赖倒置原则
510
+ - 依赖倒置原则
504
511
系统依赖于外部依赖的抽象(Dependency)而不是外部依赖的细节(DependencyImplement和DependencyLibrary),从而外部依赖的细节的变化不会影响系统
505
- - 开闭原则
512
+ - 开闭原则
506
513
要隔离更多的外部依赖,只需要增加对应的Dependency、DependencyImplement和DependencyLibrary,以及DependencyContainer增加对应的闭包变量和get、set函数即可,无需修改System;要替换外部依赖的实现,只需要对它的Dependency增加更多的DependencyImplement,然后Client改为注入新的DependencyImplement即可,无需修改System;要修改已有的外部依赖(如升级版本),只需要修改DependencyImplement和DependencyLibrary即可,无需修改System
507
514
508
515
依赖隔离模式也应用了“依赖注入”、“控制反转”的思想
@@ -513,11 +520,11 @@ export let api1 = function () {
513
520
514
521
## 优点
515
522
516
- - 提高系统的稳定性
523
+ - 提高系统的稳定性
517
524
外部依赖的变化不会影响系统
518
- - 提高系统的扩展性
525
+ - 提高系统的扩展性
519
526
可以任意替换外部依赖而不影响系统
520
- - 提高系统的可维护性
527
+ - 提高系统的可维护性
521
528
系统与外部依赖解耦,便于维护
522
529
523
530
@@ -538,23 +545,19 @@ export let api1 = function () {
538
545
539
546
- 编辑器使用的引擎、UI库等第三方库需要替换
540
547
541
- - 编辑器使用的引擎、UI库等第三方库的版本需要升级
542
-
548
+ - 编辑器使用的引擎、UI库等第三方库的版本需要升级
543
549
如需要升级编辑器使用的Three.js引擎的版本,只需要升级作为DependencyLibrary的Three.js,并修改ThreeImplement,使其使用升级后的Three.js即可
544
550
545
- - 需要增加编辑器使用的引擎、UI库等第三方库
546
-
551
+ - 需要增加编辑器使用的引擎、UI库等第三方库
547
552
如需要让编辑器在已使用Three.js引擎的基础上增加使用Babylon.js引擎,则只需要加入ThreeAndBabylonImplement,让它同时使用Three.js和Babylon.js这两个DependencyLibrary
548
553
549
554
550
555
551
556
552
557
## 注意事项
553
558
554
- - Dependency要足够抽象,才不至于在修改或增加DependencyImplement时需要修改Dependency,从而影响System
555
-
556
- 当然,在开发阶段难免考虑不足,如一开始只有一个DependencyImplement时,Dependency往往只会考虑这个DependencyImplement。这导致在增加实现该Dependency的其它DependencyImplement时发现需要修改Dependency,使其更加抽象,这样才能容纳因增加更多的DependencyImplement而带来的变化
557
-
559
+ - Dependency要足够抽象,才不至于在修改或增加DependencyImplement时需要修改Dependency,从而影响System
560
+ 当然,在开发阶段难免考虑不足,如一开始只有一个DependencyImplement时,Dependency往往只会考虑这个DependencyImplement。这导致在增加实现该Dependency的其它DependencyImplement时发现需要修改Dependency,使其更加抽象,这样才能容纳因增加更多的DependencyImplement而带来的变化
558
561
因此,我们可以允许在开发阶段修改Dependency,但是在发布前则确保Dependency已经足够抽象和稳定
559
562
560
563
<!-- - 有多少个Dependency接口,DependencyContainer就有多少个get和set函数 -->
@@ -572,7 +575,7 @@ export let api1 = function () {
572
575
573
576
- 划分4个层:外部依赖层、应用服务层、领域服务层、领域模型层,其中前者为上层,前者依赖后者
574
577
<!-- - 外部依赖都位于外部依赖层,它们是按照依赖隔离模式设计的,在运行时由用户注入 -->
575
- - 将系统的所有外部依赖都使用依赖隔离模式来隔离出去,为每个外部依赖抽象一个Dependency接口。将DependencyImplement放到最上层的外部依赖层,将Dependency放到最下层的领域模型层。
578
+ - 将系统的所有外部依赖都使用依赖隔离模式来隔离出去,为每个外部依赖抽象一个Dependency接口。将DependencyImplement放到最上层的外部依赖层,将Dependency放到最下层的领域模型层。
576
579
这是因为DependencyImplement容易变化,所以将其放到最上层,这样它的变化不会影响其它层;而Dependency非常稳定不易变化,且被领域模型依赖,所以将其放到领域模型层
577
580
<!-- 其它三层不依赖外部依赖层,而是依赖领域模型层中的Application Core(具体就是依赖Dependency) -->
578
581
- 运用领域驱动设计来设计系统,将系统的核心逻辑建模为领域模型,将其放到领域模型层
@@ -637,7 +640,7 @@ export let api1 = function () {
637
640
638
641
### 满足各种修改外部依赖的用户需求
639
642
640
- 我遇到过这种问题:3D应用开发完成后,交给3个外部用户使用。用了一段时间后,这3个用户提出了不同的修改外部依赖的要求:第一个用户想要升级3D应用依赖的引擎A,第二个用户想要替换引擎A为引擎B,第三个用户想要同时使用引擎B和升级后的引擎A。
643
+ 我遇到过这种问题:3D应用开发完成后,交给3个外部用户使用。用了一段时间后,这3个用户提出了不同的修改外部依赖的要求:第一个用户想要升级3D应用依赖的引擎A,第二个用户想要替换引擎A为引擎B,第三个用户想要同时使用引擎B和升级后的引擎A。
641
644
如果3D应用没有使用依赖隔离模式,而是使用调用引擎这个外部依赖的话,我们就需要将交付的代码修改为3个版本,分别满足3个用户的需求。交付的3个版本的代码如下:
642
645
643
646
- 使用了升级A的3D应用代码
0 commit comments