首页

源码搜藏网

首页 > 开发教程 > 软件设计 >

分享一些如何分析和设计具有动态行为的领域模型的经验

创建时间:2013-05-06 14:53  

  好久没有写文章了,最近比较忙,另一方面也是感觉自己在这方面没什么实质性的突破。但是今天终于感觉自己小有所成,有些可以值得和大家分享的东西,并且完成了两个可以表达自己想法的Demo。因此,趁现在有点时间,是写文章和大家分享的时候了。

  首先给出这两个Demo的源代码的压缩包的下载地址,因为之前有博友说他没有装VS2010而没办法运行Demo,所以这次我分别用VS2008和VS2010实现了两个版本。

  http://files.cnblogs.com/netfocus/DCIBasedDDD.rar

  下面先分享一下我最近研究的一些知识及我对这些知识的自我感悟,然后再结合Demo中的示例讲解如何将这些感悟应用到实际。 

  一、理论知识:

  我最近一直在学习下面这些东西:

  1. 面向对象分析与设计,即Object Oriented Analysis and Design(OOA\D)
  2. 领域驱动设计,即Domain Driven Design(DDD)
  3. 四色原型:MI原型、Role原型、PPT原型、Description原型
  4. DCI架构:Data Context Interaction
  5. CQRS架构: 命令查询职责分离原则,即Command Query Responsibility Segregation

  通过学习以上这些知识,让我对面向对象的分析、设计、实现有了一些新的认识。

  1. 碰到一个业务系统,我们该如何分析业务,分析需求,并最后得到一个只包含业务概念的模型?答案是通过四色原型进行业务建模。四色原型的中心思想是:一个什么什么样的人或组织或物品或地点以某种角色在某个时刻或某段时间内参与某个活动。 其中“什么什么样的”就是DESC,“人或组织或物品或地点”就是PPT,“角色”就是Role,而”某个时刻或某段时间内的某个活动"就是MI。更具体的说明请参看我之前整理的一篇文章:四色原型的学习心得分享

  2. 业务模型建好了,该如何通过面向对象的分析与设计方法来进行对象建模呢? DDD和DCI思想可以帮助我们。首先,DDD能够指导我们建立一个静态的领域模型,该领域模型能够清楚的告诉我们建立出来的对象“是什么”,但是DDD却不能很自然的解决“做什么”的问题。大家都知道DDD在对象设计的部分实际上是一种充血模型的方式,它强调对象不仅有属性还会有行为,如果行为是跨多个领域对象的,则在DDD中用领域服务解决。但是DDD却没有完整的考虑对象与对象之间的交互如何完成,虽然它通过领域服务的方式协调多个对象之间进行交互或者在应用层协调多个对象进行交互。但是在DDD中,对象往往会拥有很多不该拥有的属性或行为。在我学习了DCI架构之后,我认识到了DDD的很多不足。

  以下是DCI的核心思想: 

  3. 领域驱动设计中对象设计部分的一些要点: 

  二、结合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 UniqueId(), state)
{
}
public LibraryAccount(UniqueId id, LibraryAccountState state) : base(id, state)
{
}

#endregion

public string Number { get; privateset; }
public string OwnerName { get; privateset; }
}
public class Book : Object<UniqueId>
{
#region Constructors

public Book(BookState state) : this(new UniqueId(), state)
{
}
public Book(UniqueId id, BookState state) : base(id, state)
{
}

#endregion

public string BookName { get; private set; }
public string Author { get; private set; }
public string Publisher { get; private set; }
publicstring ISBN { get; private set; }
publicstring Description { get; private set; }
}
publicclass Library : Object<UniqueId>, ILibrary
{
private List<Book> books new List<Book>();

public Library(LibraryState state) : this(new UniqueId(), state)
{
}
public Library(UniqueId id, LibraryState state) : base(id, state)
{
if (state != null&& state.Books !=null)
{
this.books new List<Book>(state.Books);
}
}

[Mannual]
public IEnumerable<Book> Books
{
get
{
return books.AsReadOnly();
}
}

public Book TakeBook(UniqueId bookId)
{
var book
= books.Find(b => b.Id == bookId);
books.Remove(book);
return book;
}

publicvoid PutBook(Book book)
{
books.Add(book);
}

}

  以上几个实体类还有很多细节的东西需要说明,但暂时不是重点。大家可以慢慢体会为什么我要这样设计这些类,比如属性为什么是只读的?

  好了,理论上有了角色扮演者、角色,以及场景后,我们就可以写出借书和还书的完整过程了。代码如下:

private static void BorrowReturnBookExample()
{
//创建图书馆
var library new Library(null);
Repository.Add
<Library>(library);

//创建5本书
var book1 new Book(new BookState {
BookName
"C#高级编程",
Author
"Jhon Smith",
ISBN
"56-YAQ-23452",
Publisher
"清华大学出版社",
Description
"A very good book." });
var book2
new Book(new BookState {
BookName
"JQuery In Action",
Author
"Jhon Smith", ISBN ="09-BEH-23452",
Publisher
"人民邮电出版社",
Description
"A very good book." });
var book3
new Book(new BookState {
BookName
".NET Framework Programming",
Author
"Jhon Smith",
ISBN
"12-VTQ-96786",
Publisher
"机械工业出版社",
Description
"A very good book." });
var book4
new Book(new BookState {
BookName
"ASP.NET Professional Programming",
Author
"Jim Green",
ISBN
"43-WFW-87560",
Publisher
"浙江大学出版社",
Description
"A very good book." });
var book5
new Book(new BookState {
BookName
"UML and Design Pattern",
Author
"Craig Larmen",
ISBN
"87-OPM-44651",
Publisher
"微软出版社",
Description
"A very good book." });
Repository.Add
<Book>(book1);
Repository.Add
<Book>(book2);
Repository.Add
<Book>(book3);
Repository.Add
<Book>(book4);
Repository.Add
<Book>(book5);

//将这5本书添加进图书馆
library.PutBook(book1);
library.PutBook(book2);
library.PutBook(book3);
library.PutBook(book4);
library.PutBook(book5);

//创建一个图书卡卡号,用户凭卡号借书,实际过程则是用户持卡借书
var libraryAccount new LibraryAccount(new LibraryAccountState { Number = GenerateAccountNumber(10), OwnerName ="汤雪华" });
Repository.Add
<LibraryAccount>(libraryAccount);

//创建借书场景并进行场景交互
new BorrowBooksContext(
library.ActAs
<ILibrary>(),
libraryAccount.ActAs
<IBorrower>()
).Interaction(
new List<UniqueId> { book1.Id, book2.Id });

//创建还书场景并进行场景交互
new ReturnBooksContext(
library.ActAs
<ILibrary>(),
libraryAccount.ActAs
<IBorrower>()
).Interaction(
new List<UniqueId> { book1.Id });

}

  从上面的高亮代码中,我们可以清晰的看到领域对象扮演其角色参与到活动。对象在参与活动时因为扮演了某个角色,因此自然也就有了该角色所对应的行为了。但是有人已经想到了,之前我们仅仅只是定义了角色的接口,并且对象本身也不具备角色所对应的属性或行为,那么对象扮演角色时,角色的属性或行为的具体实现在哪里呢?这个问题大家自己去看Demo的源代码吧,今天太晚了,眼睛实在快要闭上了。上面我已经把整个场景的参与者角色、角色扮演者、领域对象通过什么方法扮演(ActAs)角色、如何触发场景、领域对象和角色的区别等关键问题说明清楚了。而关于如何把角色的行为注入到领域对象之中,我自己思考了很久,思考如何利用C#实现一个既优雅又能确保强类型语言的优势,但同时又能动态将角色的属性和行为注入到某个对象的设计方式,一切尽在源码之中!

0 0   标签: DDD   
上一篇:关于架构的讨论:烦人的细节
下一篇:.NET应用框架架构设计实践 - 概述

相关内容

热门推荐