前言
在面向对象编程的理念里,应用程序是对现实世界的抽象,我们经常会将现实中的事物建模为编程语言中的类/对象(“ 是什么 ”),而事物的行为则建模为方法(“ 做什么 ”)。面向对象编程有 三大基本特性 (封装、继承/组合、多态)和 五大基本原则 (单一职责原则、开放封闭原则、里氏替换原则、依赖倒置原则、接口分离原则),但知道这些还并不足以让我们设计出好的程序,于是很多方法论就涌现了出来。
近来最火的当属领域驱动设计(DDD),其中战术建模提出的实体、值对象、聚合等建模方法,能够很好的指导我们设计出符合现实世界的领域模型。但DDD也不是万能的,在某些应用场景下,按照传统的战术建模/面向对象方法设计出来的程序,也会存在可维护性差、违反单一职责原则等问题。
本文介绍的DCI建模方法可以看成是战术建模的一种辅助,在某些场景下,它可以很好的弥补DDD战术建模的一些缺点。接下来,我们将会通过一个案例来介绍DCI是如何解决DDD战术建模的这些缺点的。
本文涉及的代码归档在github项目:https://github.com/ruanrunxue/DCI-Architecture-Implementation
案例
考虑一个普通人的生活日常,他会在学校上课,也会趁着暑假去公司工作,在工作之余去公园游玩,也会像普通人一样在家吃喝玩乐。当然,一个人的生活还远不止这些,为了讲解方便,本文只针对这几个典型的场景进行建模示例。
使用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")
}
那么,根据上述方法建模出来的模型是这样的:
模型的运行方法如下:
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
实体,它有IdentityCard
、StudentCard
等数据属性,也有Eat()
、Study()
、Work()
等业务行为 ,非常符合现实世界中定义。这也是学院派所倡导的,同时拥有数据属性和业务行为的 充血模型 。
-
应用程序
+关注
关注
37文章
3265浏览量
57677 -
DCI
+关注
关注
0文章
39浏览量
6820 -
面向对象编程
+关注
关注
0文章
22浏览量
1811
发布评论请先 登录
相关推荐
评论