然而,充血模型并非完美,它也有很多問題,比較典型的是這兩個(gè):
問題一:上帝類
People
這個(gè)實(shí)體包含了太多的職責(zé),導(dǎo)致它變成了一個(gè)名副其實(shí)的上帝類。試想,這里還是裁剪了很多“人”所包含的屬性和行為,如果要建模一個(gè)完整的模型,其屬性和方法之多,無(wú)法想象。 上帝類違反了單一職責(zé)原則,會(huì)導(dǎo)致代碼的可維護(hù)性變得極差 。
問題二:模塊間耦合
School
與Company
本應(yīng)該是相互獨(dú)立的,School
不必關(guān)注上班與否,Company
也不必關(guān)注考試與否。但是現(xiàn)在因?yàn)樗鼈兌家蕾嚵?code>People這個(gè)實(shí)體,School
可以調(diào)用與Company
相關(guān)的Work()
和OffWork()
方法,反之亦然。這導(dǎo)致 模塊間產(chǎn)生了不必要的耦合,違反了接口隔離原則 。
這些問題都是工程派不能接受的,從軟件工程的角度,它們會(huì)使得代碼難以維護(hù)。解決這類問題的方法,比較常見的是對(duì)實(shí)體進(jìn)行拆分,比如將實(shí)體的行為建模成 領(lǐng)域服務(wù) ,像這樣:
type People struct {
vo.IdentityCard
vo.StudentCard
vo.WorkCard
vo.Account
}
type StudentService struct{}
func (s *StudentService) Study(p *entity.People) {
fmt.Printf("Student %+v studying\\n", p.StudentCard)
}
func (s *StudentService) Exam(p *entity.People) {
fmt.Printf("Student %+v examing\\n", p.StudentCard)
}
type WorkerService struct{}
func (w *WorkerService) Work(p *entity.People) {
fmt.Printf("%+v working\\n", p.WorkCard)
p.Account.Balance++
}
func (w *WorkerService) OffWOrk(p *entity.People) {
fmt.Printf("%+v getting off work\\n", p.WorkCard)
}
// ...
這種建模方法,解決了上述兩個(gè)問題,但也變成了所謂的 貧血模型 :People
變成了一個(gè)純粹的數(shù)據(jù)類,沒有任何業(yè)務(wù)行為。在人的心理上,這樣的模型并不能在建立起對(duì)現(xiàn)實(shí)世界的對(duì)應(yīng)關(guān)系,不容易讓人理解,因此被學(xué)院派所抵制。
到目前為止,貧血模型和充血模型都有各有優(yōu)缺點(diǎn),工程派和學(xué)院派誰(shuí)都無(wú)法說服對(duì)方。接下來,輪到本文的主角出場(chǎng)了。
DCI架構(gòu)
DCI (Data,Context,Interactive)架構(gòu)是一種面向?qū)ο蟮能浖軜?gòu)模式,在《The DCI Architecture: A New Vision of Object-Oriented Programming》一文中被首次提出。與傳統(tǒng)的面向?qū)ο笙啾?,DCI能更好地對(duì)數(shù)據(jù)和行為之間的關(guān)系進(jìn)行建模,從而更容易被人理解。
- Data ,也即數(shù)據(jù)/領(lǐng)域?qū)ο?,用來描述系統(tǒng)“是什么”,通常采用DDD中的戰(zhàn)術(shù)建模來識(shí)別當(dāng)前模型的領(lǐng)域?qū)ο螅韧贒DD分層架構(gòu)中的領(lǐng)域?qū)印?/li>
- Context ,也即場(chǎng)景,可理解為是系統(tǒng)的Use Case,代表了系統(tǒng)的業(yè)務(wù)處理流程,等同于DDD分層架構(gòu)中的應(yīng)用層。
- Interactive ,也即交互,是DCI相對(duì)于傳統(tǒng)面向?qū)ο蟮淖畲蟀l(fā)展,它認(rèn)為我們應(yīng)該顯式地對(duì)領(lǐng)域?qū)ο螅?Object )在每個(gè)業(yè)務(wù)場(chǎng)景(Context)中扮演( Cast )的角色( Role )進(jìn)行建模。 Role代表了領(lǐng)域?qū)ο笤跇I(yè)務(wù)場(chǎng)景中的業(yè)務(wù)行為(“做什么”),Role之間通過交互完成完整的義務(wù)流程 。
這種角色扮演的模型我們并不陌生,在現(xiàn)實(shí)的世界里也是隨處可見,比如,一個(gè)演員可以在這部電影里扮演英雄的角色,也可以在另一部電影里扮演反派的角色。
DCI認(rèn)為,對(duì)Role的建模應(yīng)該是面向Context的,因?yàn)樘囟ǖ臉I(yè)務(wù)行為只有在特定的業(yè)務(wù)場(chǎng)景下才會(huì)有意義。通過對(duì)Role的建模,我們就能夠?qū)㈩I(lǐng)域?qū)ο蟮姆椒ú鸱殖鋈?,從而避免了上帝類的出現(xiàn)。最后,領(lǐng)域?qū)ο笸ㄟ^組合或繼承的方式將Role集成起來,從而具備了扮演角色的能力。
DCI架構(gòu)一方面通過角色扮演模型使得領(lǐng)域模型易于理解,另一方面通過“ 小類大對(duì)象 ”的手法避免了上帝類的問題,從而較好地解決了貧血模型和充血模型之爭(zhēng)。另外,將領(lǐng)域?qū)ο蟮男袨楦鶕?jù)Role拆分之后,模塊更加的高內(nèi)聚、低耦合了。
使用DCI建模
回到前面的案例,使用DCI的建模思路,我們可以將“人”的幾種行為按照不同的角色進(jìn)行劃分。吃完、睡覺、玩游戲,是作為人類角色的行為;學(xué)習(xí)、考試,是作為學(xué)生角色的行為;上班、下班,是作為員工角色的行為;購(gòu)票、游玩,則是作為游玩者角色的行為?!叭恕痹?strong>家這個(gè)場(chǎng)景中,充當(dāng)?shù)氖侨祟惖慕巧辉?strong>學(xué)校這個(gè)場(chǎng)景中,充當(dāng)?shù)氖菍W(xué)生的角色;在公司這個(gè)場(chǎng)景中,充當(dāng)?shù)氖菃T工的角色;在公園這個(gè)場(chǎng)景中,充當(dāng)?shù)氖怯瓮嬲叩慕巧?/p>
需要注意的是,學(xué)生、員工、游玩者,這些角色都應(yīng)該具備人類角色的行為,比如在學(xué)校里,學(xué)生也需要吃飯。
最后,根據(jù)DCI建模出來的模型,應(yīng)該是這樣的:
在DCI模型中,People
不再是一個(gè)包含眾多屬性和方法的“上帝類”,這些屬性和方法被拆分到多個(gè)Role中實(shí)現(xiàn),而People
由這些Role組合而成。
另外,School
與Company
也不再耦合,School
只引用了Student
,不能調(diào)用與Company
相關(guān)的Worker
的Work()
和OffWorker()
方法。
代碼實(shí)現(xiàn)DCI模型
DCI建模后的代碼目錄結(jié)構(gòu)如下;
- context: 場(chǎng)景
- company.go
- home.go
- park.go
- school.go
- object: 對(duì)象
- people.go
- data: 數(shù)據(jù)
- account.go
- identity_card.go
- student_card.go
- work_card.go
- role: 角色
- enjoyer.go
- human.go
- student.go
- worker.go
從代碼目錄結(jié)構(gòu)上看,DDD和DCI架構(gòu)相差并不大,aggregate
目錄演變成了context
目錄;vo
目錄演變成了data
目錄;entity
目錄則演變成了object
和role
目錄。
首先,我們實(shí)現(xiàn)基礎(chǔ)角色Human
,Student
、Worker
、Enjoyer
都需要組合它:
package role
// 人類角色
type Human struct {
data.IdentityCard
data.Account
}
func (h *Human) Eat() {
fmt.Printf("%+v eating\\n", h.IdentityCard)
h.Account.Balance--
}
func (h *Human) Sleep() {
fmt.Printf("%+v sleeping\\n", h.IdentityCard)
}
func (h *Human) PlayGame() {
fmt.Printf("%+v playing game\\n", h.IdentityCard)
}
接著,我們?cè)賹?shí)現(xiàn)其他角色,需要注意的是, Student
、Worker
、Enjoyer
不能直接組合Human
,否則People
對(duì)象將會(huì)有4個(gè)Human
子對(duì)象,與模型不符:
// 錯(cuò)誤的實(shí)現(xiàn)
type Worker struct {
Human
}
func (w *Worker) Work() {
fmt.Printf("%+v working\\n", w.WorkCard)
w.Balance++
}
...
type People struct {
Human
Student
Worker
Enjoyer
}
func main() {
people := People{}
fmt.Printf("People: %+v", people)
}
// 結(jié)果輸出, People中有4個(gè)Human:
// People: {Human:{} Student:{Human:{}} Worker:{Human:{}} Enjoyer:{Human:{}}}
-
編程語(yǔ)言
+關(guān)注
關(guān)注
10文章
1931瀏覽量
34553 -
應(yīng)用程序
+關(guān)注
關(guān)注
37文章
3238瀏覽量
57550 -
DCI
+關(guān)注
關(guān)注
0文章
38瀏覽量
6799 -
面向?qū)ο缶幊?/span>
+關(guān)注
關(guān)注
0文章
22瀏覽量
1802
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論