前言
在面向?qū)ο?a target="_blank">編程的理念里,應(yīng)用程序是對現(xiàn)實世界的抽象,我們經(jīng)常會將現(xiàn)實中的事物建模為編程語言中的類/對象(“ 是什么 ”),而事物的行為則建模為方法(“ 做什么 ”)。面向?qū)ο缶幊逃?三大基本特性 (封裝、繼承/組合、多態(tài))和 五大基本原則 (單一職責原則、開放封閉原則、里氏替換原則、依賴倒置原則、接口分離原則),但知道這些還并不足以讓我們設(shè)計出好的程序,于是很多方法論就涌現(xiàn)了出來。
近來最火的當屬領(lǐng)域驅(qū)動設(shè)計(DDD),其中戰(zhàn)術(shù)建模提出的實體、值對象、聚合等建模方法,能夠很好的指導(dǎo)我們設(shè)計出符合現(xiàn)實世界的領(lǐng)域模型。但DDD也不是萬能的,在某些應(yīng)用場景下,按照傳統(tǒng)的戰(zhàn)術(shù)建模/面向?qū)ο蠓椒ㄔO(shè)計出來的程序,也會存在可維護性差、違反單一職責原則等問題。
本文介紹的DCI建模方法可以看成是戰(zhàn)術(shù)建模的一種輔助,在某些場景下,它可以很好的彌補DDD戰(zhàn)術(shù)建模的一些缺點。接下來,我們將會通過一個案例來介紹DCI是如何解決DDD戰(zhàn)術(shù)建模的這些缺點的。
本文涉及的代碼歸檔在github項目:https://github.com/ruanrunxue/DCI-Architecture-Implementation
案例
考慮一個普通人的生活日常,他會在學校上課,也會趁著暑假去公司工作,在工作之余去公園游玩,也會像普通人一樣在家吃喝玩樂。當然,一個人的生活還遠不止這些,為了講解方便,本文只針對這幾個典型的場景進行建模示例。
使用DDD建模
按照DDD戰(zhàn)術(shù)建模的思路,首先,我們會列出該案例的 通用語言 :
人、身份證、銀行卡、家、吃飯、睡覺、玩游戲、學校、學生卡、學習、考試、公司、工卡、上班、下班、公園、購票、游玩
接著,我們使用戰(zhàn)術(shù)建模技術(shù)( 值對象 、 實體 、 聚合 、 領(lǐng)域服務(wù) 、 資源庫 )對通用語言進行領(lǐng)域建模。
DDD建模后的代碼目錄結(jié)構(gòu)如下:
- aggregate: 聚合
- company.go
- home.go
- park.go
- school.go
- entity: 實體
- people.go
- vo: 值對象
- account.go
- identity_card.go
- student_card.go
- work_card.go
我們將身份證、學生卡、工卡、銀行卡這幾個概念,建模為 值對象 (Value Object):
package vo
// 身份證
type IdentityCard struct {
Id uint32
Name string
}
// 學生卡
type StudentCard struct {
Id uint32
Name string
School string
}
// 工卡
type WorkCard struct {
Id uint32
Name string
Company string
}
// 銀行卡
type Account struct {
Id uint32
Balance int
}
...
接著我們將人建模成 實體 (Entity),他包含了身份證、學生卡等值對象,也具備吃飯、睡覺等行為:
package entity
// 人
type People struct {
vo.IdentityCard
vo.StudentCard
vo.WorkCard
vo.Account
}
// 學習
func (p *People) Study() {
fmt.Printf("Student %+v studying\\n", p.StudentCard)
}
// 考試
func (p *People) Exam() {
fmt.Printf("Student %+v examing\\n", p.StudentCard)
}
// 吃飯
func (p *People) Eat() {
fmt.Printf("%+v eating\\n", p.IdentityCard)
p.Account.Balance--
}
// 睡覺
func (p *People) Sleep() {
fmt.Printf("%+v sleeping\\n", p.IdentityCard)
}
// 玩游戲
func (p *People) PlayGame() {
fmt.Printf("%+v playing game\\n", p.IdentityCard)
}
// 上班
func (p *People) Work() {
fmt.Printf("%+v working\\n", p.WorkCard)
p.Account.Balance++
}
// 下班
func (p *People) OffWork() {
fmt.Printf("%+v getting off work\\n", p.WorkCard)
}
// 購票
func (p *People) BuyTicket() {
fmt.Printf("%+v buying a ticket\\n", p.IdentityCard)
p.Account.Balance--
}
// 游玩
func (p *People) Enjoy() {
fmt.Printf("%+v enjoying park scenery\\n", p.IdentityCard)
}
最后,我們將學校、公司、公園、家建模成 聚合 (Aggregate),聚合由一個或多個實體、值對象組合而成,組織它們完成具體的業(yè)務(wù)邏輯:
package aggregate
// 家
type Home struct {
me *entity.People
}
func (h *Home) ComeBack(p *entity.People) {
fmt.Printf("%+v come back home\\n", p.IdentityCard)
h.me = p
}
// 執(zhí)行Home的業(yè)務(wù)邏輯
func (h *Home) Run() {
h.me.Eat()
h.me.PlayGame()
h.me.Sleep()
}
// 學校
type School struct {
Name string
students []*entity.People
}
func (s *School) Receive(student *entity.People) {
student.StudentCard = vo.StudentCard{
Id: rand.Uint32(),
Name: student.IdentityCard.Name,
School: s.Name,
}
s.students = append(s.students, student)
fmt.Printf("%s Receive stduent %+v\\n", s.Name, student.StudentCard)
}
// 執(zhí)行School的業(yè)務(wù)邏輯
func (s *School) Run() {
fmt.Printf("%s start class\\n", s.Name)
for _, student := range s.students {
student.Study()
}
fmt.Println("students start to eating")
for _, student := range s.students {
student.Eat()
}
fmt.Println("students start to exam")
for _, student := range s.students {
student.Exam()
}
fmt.Printf("%s finish class\\n", s.Name)
}
// 公司
type Company struct {
Name string
workers []*entity.People
}
func (c *Company) Employ(worker *entity.People) {
worker.WorkCard = vo.WorkCard{
Id: rand.Uint32(),
Name: worker.IdentityCard.Name,
Company: c.Name,
}
c.workers = append(c.workers, worker)
fmt.Printf("%s Employ worker %s\\n", c.Name, worker.WorkCard.Name)
}
// 執(zhí)行Company的業(yè)務(wù)邏輯
func (c *Company) Run() {
fmt.Printf("%s start work\\n", c.Name)
for _, worker := range c.workers {
worker.Work()
}
fmt.Println("worker start to eating")
for _, worker := range c.workers {
worker.Eat()
}
fmt.Println("worker get off work")
for _, worker := range c.workers {
worker.OffWork()
}
fmt.Printf("%s finish work\\n", c.Name)
}
// 公園
type Park struct {
Name string
enjoyers []*entity.People
}
func (p *Park) Welcome(enjoyer *entity.People) {
fmt.Printf("%+v come to park %s\\n", enjoyer.IdentityCard, p.Name)
p.enjoyers = append(p.enjoyers, enjoyer)
}
// 執(zhí)行Park的業(yè)務(wù)邏輯
func (p *Park) Run() {
fmt.Printf("%s start to sell tickets\\n", p.Name)
for _, enjoyer := range p.enjoyers {
enjoyer.BuyTicket()
}
fmt.Printf("%s start a show\\n", p.Name)
for _, enjoyer := range p.enjoyers {
enjoyer.Enjoy()
}
fmt.Printf("show finish\\n")
}
那么,根據(jù)上述方法建模出來的模型是這樣的:
模型的運行方法如下:
paul := entity.NewPeople("Paul")
mit := aggregate.NewSchool("MIT")
google := aggregate.NewCompany("Google")
home := aggregate.NewHome()
summerPalace := aggregate.NewPark("Summer Palace")
// 上學
mit.Receive(paul)
mit.Run()
// 回家
home.ComeBack(paul)
home.Run()
// 工作
google.Employ(paul)
google.Run()
// 公園游玩
summerPalace.Welcome(paul)
summerPalace.Run()
貧血模型 VS 充血模型(工程派 VS 學院派)
上一節(jié)中,我們使用DDD的戰(zhàn)術(shù)建模完成了該案例領(lǐng)域模型。模型的核心是People
實體,它有IdentityCard
、StudentCard
等數(shù)據(jù)屬性,也有Eat()
、Study()
、Work()
等業(yè)務(wù)行為 ,非常符合現(xiàn)實世界中定義。這也是學院派所倡導(dǎo)的,同時擁有數(shù)據(jù)屬性和業(yè)務(wù)行為的 充血模型 。
-
應(yīng)用程序
+關(guān)注
關(guān)注
37文章
3265瀏覽量
57677 -
DCI
+關(guān)注
關(guān)注
0文章
39瀏覽量
6820 -
面向?qū)ο缶幊?/span>
+關(guān)注
關(guān)注
0文章
22瀏覽量
1811
發(fā)布評論請先 登錄
相關(guān)推薦
評論