如何建模:写的太好了!

好久没有写文章了,

最近比较忙,

另一方面也是感觉自己在这方面没什么实质性的突破。

但是今

天终于感觉自己小有所成,

有些可以值得和大家分享的东西,

并且完成了两个可以表达自己想法

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) 

    { 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值