好久没有写文章了,
最近比较忙,
另一方面也是感觉自己在这方面没什么实质性的突破。
但是今
天终于感觉自己小有所成,
有些可以值得和大家分享的东西,
并且完成了两个可以表达自己想法
的
Demo
。因此,趁现在有点时间,是写文章和大家分享的时候了。
首先给出这两个
Demo
的源代码的压缩包的下载地址,
因为之前有博友说他没有装
VS2010
而
没办法运行
Demo
,所以这次我分别用
VS2008
和
VS2010
实现了两个版本。
http://files.cnblogs.com/netfocus/DCIBasedDDD.rar
下面先分享一下我最近研究的一些知识及我对这些知识的自我感悟,然后再结合
Demo
中的示
例讲解如何将这些感悟应用到实际。
一
.
理论知识:
我最近一直在学习下面这些东西:
面向对象分析与设计,即
Object Oriented Analysis and Design
(
OOA\D
)
领域驱动设计,即
Domain Driven Design
(
DDD
)
四色原型:
MI
原型、
Role
原型、
PPT
原型、
Description
原型
DCI
架构:
Data Context Interaction
CQRS
架构:
命令查询职责分离原则,即
Command Query Responsibility
Segregation
通过学习以上这些知识,让我对面向对象的分析、设计、实现有了一些新的认识。
1.
碰到一个业务系统,我们该如何分析业务,分析需求,并最后得到一个只包含业务概念的模
型?答案是通过四色原型进行业务建模。
四色原型的中心思想是:
一个什么什么样的人或组织或
物品或地点以某种角色在某个时刻或某段时间内参与某个活动。
其中
“
什么什么样的
”
就是
DESC
,
“
人或组织或物品或地点
”
就是
PPT
,
“
角色
”
就是
Role
,而
”
某个时刻或某段时间内的某
个活动
"
就是
MI
。更具体的说明请参看我之前整理的一篇文章:
http://www.cnblogs.com/netfocus/archive/2011/03/05/1971899.html
2.
业务模型建好了,该如何通过面向对象的分析与设计方法来进行对象建模呢?
DDD
和
DCI
思想可以帮助我们。首先,
DDD
能够指导我们建立一个静态的领域模型,该领域模型能够清楚
的告诉我们建立出来的对象
“
是什么
”
,但是
DDD
却不能很自然的解决
“
做什么
”
的问题。大家都
知道
DDD
在对象设计的部分实际上是一种充血模型的方式,它强调对象不仅有属性还会有行
为,如果行为是跨多个领域对象的,则在
DDD
中用领域服务解决。但是
DDD
却没有完整的考
虑对象与对象之间的交互如何完成,
虽然它通过领域服务的方式协调多个对象之间进行交互或者
在应用层协调多个对象进行交互。
但是在
DDD
中,
对象往往会拥有很多不该拥有的属性或行为。
在我学习了
DCI
架构之后,我认识到了
DDD
的很多不足。
以下是
DCI
的核心思想:
对象扮演某个角色进入场景,然后在场景中进行交互,场景的参与者就是对象所扮演的
角色;
一个对象可以扮演多个角色,一个角色也可以被多个对象扮演;
对象的属性和行为分为:
A
:核心属性和行为,这些属性或行为是不依赖于任何场景的;
B:
场景属性和行为,
对象通过扮演某个角色进入某个特定场景时拥有的属性或行为,
一
旦对象离开了这个场景,不再扮演了这个角色后,这些场景属性或行为也就不再属于该
对象了;比如人有核心的属性和行为:身高、体重、吃饭、睡觉,然后当人扮演教师的
角色在教室里上课时,他则具有上课的行为,
一旦回到家里,就又变成了一个普通的人;
比如一个物品,在生产时叫产品,在销售时叫商品,坏了的时候叫废品,它在不同阶段
扮演不同的角色所具有的属性是不一样的;
场景的生命周期,
场景是一个时间与空间的结合,
可以理解为某个活动;
一旦活动结束,
则场景也就消失;
DCI
中的
D
可以理解为
DDD
中的领域模型;场景中交互的是角色,而不是领域实体。
场景属于
DSL
的思考层面,更接近于需求和用例。
而领域也是伟大的出现,
但是不能为
了领域而领域,为什么呢?因为场景是大哥用例是大哥。领域的存在是为了控制固定概
念的部分,这样在某种成度上控制了一定的复杂性和提高了可控性,
而
DCI
则解决了可
变性和需求的问题。从某种意义上来说,
“
领域层(在
DCI
中可能不会太凸显领域层,
不如
OLD DDD
那么凸显)
”
是为了
DCI
架构服务的。
角色是人类的主观意识,用于对象分析和设计阶段,但是在运行阶段,角色和对象实体
是一体的,软件运行过程中只有对象,只是这些对象在参与某个活动时扮演了某个角色
而已;
3.
领域驱动设计中的对象设计部分的一些要点:
DDD
的在对象设计方面的最大贡献之处在于其实体、值对象,以及聚合边界的三个部
分,通过这三个概念,我们可以将对象的静态结构设计好。
领域对象所包含的属性必须是只读的,只读的含义是一旦对象被创建好,则只有对象自
己才能修改其属性,属性的类型可能是基本数据类型或值类型,即
ValueObject
;
领域模型设计时不应考虑
ORM
等技术性的东西,而应该只专注于业务,不要让你的领
域模型依赖于技术性的东西;
领域对象的属性和方法设计时要完全根据业务的含义和需要来进行,不要动不动就把每
个属性定义为
get;set
,这会导致领域模型的不安全
;
仓储
(
Repository
)
不是解决让领域模型不依赖于外部数据存储的唯一方式,
我觉得还
有更优雅的方式那就是事件驱动;
设计领域模型时不要考虑分层架构方面的东西,因为领域模型与分层架构无关;
不要认为领域模型可以做任何事情,比如查询。领域模型只能帮你处理业务逻辑,你不
要用它来帮你做查询的工作,那不是它擅长的领地,因为它的存在目的不是为了查询;
CQRS
的思想就是指导我们:
命令和查询因该完全分离,
领域模型适合处理命令的部分,
而查询可以用其他任何的不依赖于领域模型的技术来实现,甚至可以直接写
SQL
也可
以;
分析领域模型及其对象之间的交互时,要分清什么是交互的参与者,什么是交互的驱动
者,通常情况下,比如人是交互的驱动者,而人在系统中注册的某个帐号所扮演的角色
就是交互的参与者;比如我用
A
的图书卡去图书馆借书,则我是借书活动的驱动者,而
A
的图书卡对应的帐号所扮演的借书者(
Borrower
)角色就是借书活动的参与者;
二
.
结合
Demo
讲解如何将理论应用到实际:
前面的介绍看起来比较枯燥,
但对我来说是非常宝贵的经验积累。
下面我通过一个例子分析如何
运用这些知识:
以图书管理系统中的借书和还书的场景进行说明:
1.
借书场景:某个人拿着某张借书卡去图书馆借书;
2.
还书场景:某个人拿着某张借书卡去图书馆还书;
根据四色原型的分析方法,我们可以得出:
某个
“
人
”
以图书借阅者的角色向图书馆借书。
从这里
我们可以得出三个角色:
1
)借阅者(
Borrower
);
2
)被借的图书(
BorrowedBook
);
3
)
图书馆。那么这三个角色的扮演者对象是谁呢?其实这是问题的关键!
1
)
是谁扮演了借阅者这个角色?很多人认为是走进图书馆的那个人,
其实不是。
人所持的图书
卡对应的那个人才是真正的借阅者角色的扮演者;
试想张三用李四的图书卡借书,
借书的是谁?
应该是李四,
此时相当于李四被张三操控了而已;
当然这里假设图书馆不会对持卡人和卡的真正
拥有者进行身份核对。
所以,
借阅者角色的扮演者应该是借书卡对应的帐号
(借书卡帐号本质上
是某个人在图书馆里系统中的镜像)
。
那么图书卡帐号和借阅者角色有什么区别?图书卡帐号是
一个普通的领域对象,只包含一些核心的基本的属性,如
AccountNumber
,
Owner
等;但是
Borrower
角色则具有借书还书的行为;
2
)是谁扮演了被借的书这个角色?这个问题比较好理解,肯定是图书了。那图书和被借的图书
有什么区别吗?大家都知道图书是指还没被借走的还是放在书架上的书本,
而被借的书则包含了
更多的含义,比如被谁借的,什么时候借的,等等;
3
)为什么图书馆也是一个角色?图书馆只是一个地点,它不管有没有参与到借书场景中,都叫
图书馆,并且它的属性也不会因为参与到场景中而改变。没错!
但是他确实是一个角色,只不过
它比较特殊,因为在参与到借书场景时它是
“
本色演出
”
,
即它本身就是一个角色;
举两个其他的
例子你可能就好理解一点了:比如教室,上课时是课堂,考试时是考场;比如土地,建造房子时
是工地,种植粮食时是田地,是有可能增加依赖场景的行为和属性的。
有了场景和角色的之后,
我们就可以写出角色在场景中交互的代码了。
我们此时完全不用去考虑
对象如何设计,更不用考虑如何存储之类的技术性东西。因为我们现在已经清晰的分析清楚
1
)
场景参与者;
2
)参与者
“
做什么
”
;代码如下,应该比较好懂:
///
<summary>
///
借阅者角色定义
///
</summary>
public
interface
IBorrower : IRole<UniqueId>
{
IEnumerable<IBorrowedBook> BorrowedBooks {
get
; }
//
借了哪些书
void
BorrowBook(Book book);
//
借书行为
Book ReturnBook(UniqueId bookId);
//
还书行为
}
///
<summary>
///
图书馆角色定义
///
</summary>
public
interface
ILibrary : IRole<UniqueId>
{
IEnumerable<Book> Books {
get
; }
//
总共有哪些书
Book TakeBook(UniqueId bookId);
//
书的出库
void
PutBook(Book book);
//
书的入库
}
///
<summary>
///
被借的书角色定义
///
</summary>
public
interface
IBorrowedBook : IRole<UniqueId>
{
Book Book {
get
; }
//
书
DateTime BorrowedTime {
get
; }
//
被借时间
}
///
<summary>
///
借书场景
///
</summary>
public
class
BorrowBooksContext
{
private
ILibrary library;
//
场景参与者角色
1
:图书馆角色
private
IBorrower borrower;
//
借书参与者角色
2
:借阅者角色
public
BorrowBooksContext(ILibrary library, IBorrower borrower)
{
this
.library = library;
this
.borrower = borrower;
}
///
<summary>
///
启动借书场景,各个场景参与者开始进行交互
///
</summary>
public
void
Interaction(IEnumerable<UniqueId> bookIds)
{
foreach
(var bookId
in
bookIds)
{
borrower.BorrowBook(library.TakeBook(bookId));
//
}
}
}
///
<summary>
///
还书场景
///
</summary>
public
class
ReturnBooksContext
{
private
ILibrary library;
private
IBorrower borrower;
public
ReturnBooksContext(ILibrary library, IBorrower borrower)
{
this
.library = library;
this
.borrower = borrower;
}
public
void
Interaction(IEnumerable<UniqueId> bookIds)
{
foreach
(var bookId
in
bookIds)
{
library.PutBook(borrower.ReturnBook(bookId));
}
}
}
接下来考虑角色扮演者如何设计与实现:
角色扮演者就是
DDD
中的领域对象,在这个例子中主要有:借书卡帐号(
LibraryAccount
)、
书本(
Book
)、图书馆(
Library
);下面是这几个实体类的实现:
public
class
LibraryAccount : Object<UniqueId>
{
#region
Constructors
public
LibraryAccount(LibraryAccountState state) :
this
(
new
Uniqu
eId(), state)
{
}
public
LibraryAccount(UniqueId id, LibraryAccountState state) :
b
ase
(id, state)
{
}
#endregion
public
string
Number {
get
;
private
set
; }
public
string
OwnerName {
get
;
private
set
; }
}
public
class
Book : Object<UniqueId>
{
#region
Constructors
public
Book(BookState state) :
this
(
new
UniqueId(), state)
{