如何实现DCI架构(上)

电子说

1.3w人已加入

描述

前言

在面向对象编程的理念里,应用程序是对现实世界的抽象,我们经常会将现实中的事物建模为编程语言中的类/对象(“ 是什么 ”),而事物的行为则建模为方法(“ 做什么 ”)。面向对象编程有 三大基本特性 (封装、继承/组合、多态)和 五大基本原则 (单一职责原则、开放封闭原则、里氏替换原则、依赖倒置原则、接口分离原则),但知道这些还并不足以让我们设计出好的程序,于是很多方法论就涌现了出来。

近来最火的当属领域驱动设计(DDD),其中战术建模提出的实体、值对象、聚合等建模方法,能够很好的指导我们设计出符合现实世界的领域模型。但DDD也不是万能的,在某些应用场景下,按照传统的战术建模/面向对象方法设计出来的程序,也会存在可维护性差、违反单一职责原则等问题。

本文介绍的DCI建模方法可以看成是战术建模的一种辅助,在某些场景下,它可以很好的弥补DDD战术建模的一些缺点。接下来,我们将会通过一个案例来介绍DCI是如何解决DDD战术建模的这些缺点的。

本文涉及的代码归档在github项目:https://github.com/ruanrunxue/DCI-Architecture-Implementation

案例

考虑一个普通人的生活日常,他会在学校上课,也会趁着暑假去公司工作,在工作之余去公园游玩,也会像普通人一样在家吃喝玩乐。当然,一个人的生活还远不止这些,为了讲解方便,本文只针对这几个典型的场景进行建模示例。

DCI

使用DDD建模

按照DDD战术建模的思路,首先,我们会列出该案例的 通用语言

人、身份证、银行卡、家、吃饭、睡觉、玩游戏、学校、学生卡、学习、考试、公司、工卡、上班、下班、公园、购票、游玩

接着,我们使用战术建模技术( 值对象实体聚合领域服务资源库 )对通用语言进行领域建模。

DDD建模后的代码目录结构如下:

- 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),聚合由一个或多个实体、值对象组合而成,组织它们完成具体的业务逻辑:

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
}
// 执行Home的业务逻辑
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)
}
// 执行School的业务逻辑
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)
}
// 执行Company的业务逻辑
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)
}
// 执行Park的业务逻辑
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")
}

那么,根据上述方法建模出来的模型是这样的:

DCI

模型的运行方法如下:

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 学院派)

上一节中,我们使用DDD的战术建模完成了该案例领域模型。模型的核心是People实体,它有IdentityCardStudentCard等数据属性,也有Eat()Study()Work()等业务行为 ,非常符合现实世界中定义。这也是学院派所倡导的,同时拥有数据属性和业务行为的 充血模型

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分