专注于面向对象、领域驱动设计,及软件架构方面的学习

要学会站在巨人的肩膀上让自己成长。QQ:94388050

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
  39 随笔 :: 0 文章 :: 355 评论 :: 0 引用

好久没有写文章了,最近比较忙,另一方面也是感觉自己在这方面没什么实质性的突破。但是今天终于感觉自己小有所成,有些可以值得和大家分享的东西,并且完成了两个可以表达自己想法的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。更具体的说明请参看我之前整理的一篇文章: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 UniqueId(), state)
    {
    }
    
public LibraryAccount(UniqueId id, LibraryAccountState state) : base(id, state)
    {
    }

    
#endregion

    
public string Number { getprivate set; }
    
public string OwnerName { getprivate set; }
}
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 { getprivate set; }
    
public string Author { getprivate set; }
    
public string Publisher { getprivate set; }
    
public string ISBN { getprivate set; }
    
public string Description { getprivate set; }
}
public class 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;
    }

    
public void 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#实现一个既优雅又能确保强类型语言的优势但同时又能动态将角色的属性和行为注入到某个对象的设计方式,一切尽在源码之中!

标签: DDD, 领域建模, DCI
posted on 2011-07-10 01:59 netfocus 阅读(4022) 评论(28) 编辑 收藏

评论

#1楼 2011-07-10 07:21 桀骜的灵魂      
写得太精彩了,我获益最大的是角色扮演者的分析,哈哈,强烈推荐!
 回复 引用 查看   

#2楼 2011-07-10 09:33 LkSh      
不错,最近对面向对象、及多层架构有更深一点的了解,再来看这种文章就不觉得吃力了。反倒能学到点东西。
 回复 引用 查看   

#3楼 2011-07-10 09:53 lihangnet      
“根据四色原型的分析方法,我们可以得出:某个“人”以图书借阅者的角色向图书馆借书。从这里我们可以得出三个角色:1)借阅者(Borrower);2)被借的图书(BorrowedBook);3)图书馆。”
这样做有没有角色分析过度的嫌疑,我对角色(Role)的理解是权限的集合,更多指的是人在某一上下文(Context)能做什么,毕竟在业务分析中说明人要干什么更自然些。
在这样一个相对简单的用例中,就要思考这么多角色的扮演者对象是谁?这的确是DCI问题的关键,但似乎很难和别人达成共识,比如说我就不认为图书卡是真正的借阅者角色的扮演者,我认为图书管理员才是,或者如楼主说的"很多人所认为的走进图书馆的那个人"!当然这只是一个例子,争论谁到底是“借阅者角色”都不重要,重要的是我们能在借书这一领域对于这些对象能达成一致吗?如果不能自然的达成一致,还需要大量的解释(见楼主的解释说明内容),对于个人对业务的分析或许是有用的(你可以用它来扩充对象的行为,并根据这些分析编写代码),对于领域模型构建则无用。
DCI只不过是把原本是充血模型中对象行为的设定延迟到了所谓的“角色”上,并没有从根本解决如何充血模型中对象行为设定的问题,实际上我认为这些行为也的确很难确定,类职责的分配是OOD中最大的问题,很难说有“银弹”,只能是个别问题个别分析,“一劳永逸”的想靠技术解决行不通。
从另一个角度来看,我倒是觉得“贫血模型”倒是能解决领域模型的构建,毕竟不包括行为只包括属性的对象还是相对稳定的,书也好,图书馆也好,图书卡也好,他们的属性是不会有太大变化的,即使有扩充起来也简单的多。用事务类封装行为,用服务(Services)包装事务,用人扮演的角色操作服务可能和分析出的业务用例更切合一些。我在读DDD时,始终认为Eric Evans的领域模型本意还是更贴近“贫血模型”(当然这是我个人的看法)。
对业务分析的越深入,可能越觉得自己对领域模型把握的越透彻,就很容易在对象中添加“个性化”的行为,尤其是写程序的人还进行业务分析,如果跳出来,让一个不懂编程的人(纯业务人员)或一个不懂业务的人(纯程序员)来看这个领域模型,他们都是很难理解的。领域模型一旦失去了沟通的意义,往往就僵化了,失去作用了,如果只是分析人员懂,那就可能出现偏差和错误,最终导致谁都不愿碰那个领域模型。
以上是个人的一些看法,看到楼主的文章有感而发,还是很感谢楼主关于这方面的探讨。
另外,“贫血”和“充血”之争每隔一段时间都会重来。但有些讨论过多的关注技术层面,多在数据持久化、DTO、服务层方面纠结(当然这也很重要)。我更关心的是如何从业务到系统的“贫血”和“充血”,而不是系统的实现技术。希望这方面的讨论能更多一些。
 回复 引用 查看   

#4楼[楼主] 2011-07-10 10:29 netfocus      
@lihangnet
感谢你的回复。

1)先分清一下角色的概念吧:我在本文中的角色的含义和你所说的角色(权限的集合)应该是两个概念,你的是传统意义上的User-Role-Permission中的Role,而我所说的角色则是指活动场景的参与者身份,可以理解为拍电视剧时电视剧里的人物的角色;不仅仅是人可以扮演角色,事物、地点也可以扮演;这点我在文中已经分析过了;

2)关于角色及其扮演者的分析与结论,我也感觉有点难达成共识,看一下我发的前一篇文章就知道了。在分析阶段:我会从活动出发,分析活动参与者,即角色,然后再思考角色扮演者,从而完成整个分析;而在设计编码阶段:则从建模开始,先设计领域对象即角色扮演者,再设计角色,再编写角色交互的场景相关的代码;这是两个相反的过程;我个人认为我们在分析业务时,或者说用户在谈业务需求时,总是围绕活动或过程展开,他不会说这个聚合怎么样,这个实体该有哪些属性或行为;我个人认为Eric Evans的那套DDD模型还是很难与用户沟通,虽然程序员相对容易理解。但很多时候程序员也会产生很多理解不一致,比如聚合根的问题上,往往理解差别很大;我觉得我们在分析事物时引入角色的概念,是为了和用户大脑的思考拉近距离,实际上用户头脑中产生也都是角色,比如一个人被谋杀了,人们想到的肯定是被害人、凶手等概念,而这些概念不正是角色吗?被害人和凶手都由人扮演,如果我们把被害人的属性和凶手的属性和行为全部放在人这个实体上,显然不太合理,会导致人的职责过于庞大,虽然不违背分析原理,但违背设计原理。

3)关于你提到的DCI本质上没有解决职责如何分配的问题,确实没有!就像你所说,它只是把对象的一些依赖场景的属性和行为分散到了角色中而已;这样做也许对最终效果来说没有区别,你把所有属性和行为都放在一个对象中也可以;我认为DCI的真正好处是让我们的代码看起来和用户的想法更接近,用户说了什么交互场景,什么活动,我们的代码中也就会有什么场景,会有相应的场景参与者角色,这样做极大地提高了代码的可读性;我认为这才是DCI的真正意义所在;而这也是为什么我文中说DDD中的领域对象是静态的,只注重静态结构的原因;

4)你可能会角色这样可能会导致角色漫天飞,有点过度设计。确实,所以有时我们在真正实现的时候,可以考虑让领域对象本身就实现某些角色,这样就不用新定义角色类,但同时也不破坏DCI的理念;在我的示例源码中的图书馆这个实体类就是本色出演图书馆角色;

5)关于你提到的用贫血模型+面向过程+分层架构的开发方法,确实很容易入手,在一定程度上也很容易维护;但它同时也放弃了面向对象的理念,因为此时的对象本质上和结构体(存放数据的容器)没有本质区别;这已经不能算是真正的面向对象开发;另外,我理解的Eric Evans的DDD中的领域模型是充血的;
 回复 引用 查看   

#5楼 2011-07-10 12:01 菜鸟老了      
图书馆借书是否理解为 操作员把图书从存储库A(图书馆)转存到存储库B(借书者)中?
 回复 引用 查看   

#6楼 2011-07-10 12:05 lihangnet      
刚刚看了Jdon他们对图书馆借书活动的讨论,程序员之间达成对业务理解的一致太难了,尤其是对“资深”一点的程序员。用户反而好一些,因为尽管他们业务经验丰富,但毕竟没有用系统化的方式把需求总结出来,这时候用类似DCI或四色架构型的方式去描述需求,用户是可以接受的(用其他结构化的方式用户可能也同样会认同的)。现在的问题集中在2个方面:
(1)如何说服其他程序员接受共同的领域模型?或者说如何保持思考业务时的思维方向一致性?
(2)我们描述的业务是正确的吗?我们毕竟不是行业专家,业务人员(尤其是中高层)往往即没有时间也没有意愿帮我们修订模型(尽管他们接受我们的领域模型,但也仅是接受而非理解和赞同,他们没有义务)
怎么解决上述2个问题?

另外,我最近一直思考,面向对象真的适合业务系统吗?业务往往是一个过程,业务本身似乎用过程思考跟直观一些,反而是一些架构上的基础设施的东西用面向对象更加贴切。
我还有一个观点,就是:业务(过程)是多变的,而数据是相对稳定的,DCI是一种方法,通过角色解决业务在不同上下文的变化,但还是流于琐碎,如果减少角色或者让角色自然一些(更符合常识),DCI还是有价值。
 回复 引用 查看   

#7楼[楼主] 2011-07-10 12:15 netfocus      
@菜鸟老了
图书馆借书的本质是:书从图书馆转移到借书者手中。这是借书的本质,但是实际生活中会有很多步骤,比如借阅者把书拿到借书处,操作员扫描书本,扫描借书卡,等过程。如果以后图书馆变先进了,连操作员都不需要了。就像你去银行柜台取钱和ATM取钱一样,取钱的本质是一样的,但是如果通过ATM机取钱是没有柜台营业员这个角色的,因为ATM已经做了柜台营业员该做的事情。所以我们建模时不应该把营业员,图书卡卡扫描仪等概念考虑到模型中,因为这些都不是借书或取钱的本质,如果你把这些概念考虑进去,那以后图书操作员没有了,那么你设计的模型也需要改变了;
 回复 引用 查看   

#8楼 2011-07-10 13:30 lihangnet      
引用netfocus:
@菜鸟老了
图书馆借书的本质是:书从图书馆转移到借书者手中。这是借书的本质...,那以后图书操作员没有了,那么你设计的模型也需要改变了;

问题就在这里,我们都知道图书馆借书的本质是“书从图书馆转移到借书者手中。”这可以看作一个业务用例,但具体实现步骤是不一样的,或者说我们理解的不一样的。早年我借书的时候还真没有借书卡这东西,只有图书操作员做个记录,因此设计模型还的确是需要改变。
可以说,菜鸟老了的分析方法和楼主的分析方法,亦或是Jdon他们的方法都可以完成“书从图书馆转移到借书者手中”这一用例,而且都可以很好的完成。我们怎样说服别人采用我的模型呢?
 回复 引用 查看   

#9楼 2011-07-10 13:54 菜鸟老了      
@netfocus
操作员实际建模的时候把他建立成ValueObject对象,这个时候不管最后是否存在实际的操作员,但是操作借书和还书的这两个动作还是存在的。
当操作是由我们手动完成的时候,我们就是操作员
 回复 引用 查看   

#10楼 2011-07-10 13:58 菜鸟老了      
@lihangnet
每个业务出来的模型不同人是不一样的 但是最后的最优解肯定是一样的
正确的模型有多个,但是正确的最优解有且只有一个。
至于怎么说服他人采用我们的模型,这就是首席架构师或设计师存在的必要性了。
 回复 引用 查看   

#11楼 2011-07-10 14:01 菜鸟老了      
@lihangnet
我现在最大的问题不是如何说服别人采用我的模型,而是如何说服别人改变错误的设计方式,挺悲剧的
 回复 引用 查看   

#12楼 2011-07-10 14:06 菜鸟老了      
@netfocus
@lihangnet
我是初学者,希望能有机会和各位前辈交流,442665364这是我的qq
 回复 引用 查看   

#13楼 2011-07-10 16:46 桀骜的灵魂      
博主,四色原型里面,Role和MI是否可以映射成设计里面的接口和接口的实现类?
 回复 引用 查看   

#14楼[楼主] 2011-07-10 18:06 netfocus      
@lihangnet
说服别人采用自己所想的模型是比较困难的,让别人的设计思维跟自己一样更困难。但是如果你在项目组中有足够地位,就可以做这个决定,别人付服从这个决定。条条大路通罗马,其实也没有最优的方案,哪个最优,每个人站在不同的角度上看得出的结果都是不一样的。我觉得我们能做的,就是尽量将自己所想到的认为最正确的方案想的更透彻,考虑的更全面,然后提出来和别人讨论,至于最后能否采用,不是你一个人说了算的。
 回复 引用 查看   

#15楼[楼主] 2011-07-10 18:10 netfocus      
引用桀骜的灵魂:博主,四色原型里面,Role和MI是否可以映射成设计里面的接口和接口的实现类?


我是这么认为的。首先Role表示角色,该角色可以被很多实体扮演,就像接口可以被很多对象实现一样。同样一个实体可以扮演多个接口,表示一个实体可以扮演多个角色;而MI则是一个场景,一个临时的活动,我一般把它设计为普通的类。
 回复 引用 查看   

#16楼 2011-07-10 20:13 桀骜的灵魂      
引用netfocus:
引用桀骜的灵魂:博主,四色原型里面,Role和MI是否可以映射成设计里面的接口和接口的实现类?


我是这么认为的。首先Role表示角色,该角色可以被很多实体扮演,就像接口可以被很多对象实现一样。同样一个实体可以扮演多个接口,表示一个实体可以扮演多个角色;而MI则是一个场景,一个临时的活动,我一般把它设计为普通的类。

1.博主你也认同这种想法?
2.还有Role作为接口,存在特有的属性是否存在问题?
 回复 引用 查看   

#17楼[楼主] 2011-07-10 20:52 netfocus      
引用桀骜的灵魂:
2.还有Role作为接口,存在特有的属性是否存在问题?


不太明白你的意思。
 回复 引用 查看   

#18楼 2011-07-11 14:03 dudu      
学习了,在Domain中引入Context, Role的思路不错!
 回复 引用 查看   

#19楼 2011-07-11 14:06 dax.net      
hi, netfocus,
能否讲解一下通用语言在DCI上的应用?比如,采用DCI的方式如何使得通用语言能够成为domain experts与developer之间的沟通桥梁?
 回复 引用 查看   

#20楼[楼主] 2011-07-11 17:41 netfocus      
引用dax.net:
hi, netfocus,
能否讲解一下通用语言在DCI上的应用?比如,采用DCI的方式如何使得通用语言能够成为domain experts与developer之间的沟通桥梁?

呵呵,这个我还没研究过,以后慢慢研究学习,你有什么好的经验也多分享下吧,大家一起努力。
 回复 引用 查看   

#21楼 2011-07-12 16:40 zhangmeiqing      
先收藏,留着以后慢慢品尝!
 回复 引用 查看   

#22楼 2011-07-26 13:52 土豆烤肉      
精彩!!!
感觉楼主的Context上下文与DomainService是一个概念吧。。始终于业务场景相关的!
个人觉得领域模型的业务逻辑可以按照一定的粒度进行划分:
1.领域模型本身的业务逻辑,如图书馆的收录图书业务等
2.多个领域模型的业务逻辑组合而成的业务逻辑,如还书给图片馆,分解开来:即 借书人模型还书 + 图书馆收录图书 + 。。。

因此:
1.模型本身的业务逻辑可以直接写在模型中,即充血模式;
2.多个模型业务逻辑的组合而成的业务逻辑,楼主用的是上下文方式,而小弟用的是DomainService。。

不知道个人看法是否正确。。
 回复 引用 查看   

#23楼[楼主] 2011-07-26 22:29 netfocus      
to 土豆烤肉:
Context和DomainService这两个看起来类似,但还是有区别的。
DomainService中没有包含角色的概念,至少不强调,只强调了领域对象在相互作用;

而Context则是指各个场景参与者(角色)在相互作用;而角色和领域对象是两个概念;很多时候角色和领域对象是完全相同的对象,这种情况可以看成是领域对象本色参与Context活动;而当领域对象以某个角色参与到Context中时如果该角色多出了依赖于Context上下文的相关属性或行为时,此时的角色和领域对象就有所区别了。

一般区分哪些属性和行为是属于领域对象的,哪些属性和行为是属于角色的原则我认为是:1)判断属性是否是无场景无关的,如果无关,则属于领域对象,如果依赖于场景则属于角色;2)判断行为所需要的数据是否依赖于场景,即是否需要从其他领域对象获取,如果是则该行为属于角色;

比如BorrowedTime这个属性不属于书本领域对象,而属于书本所扮演的角色BorrowedBook,即被借的书;普通的书不应该有BorrowedTime这个信息;

另外判断某个角色是否是某个领域对象本色出演我觉得还有一个方法就是看角色是否可能被不同类型的对象扮演,如果可能,则意味着有必要抽象出角色的概念,否则直接用领域对象直接本色出演即OK了。

但是我最后想说的是,一般情况下还是不要把设计搞的很复杂,也不要过分纠结于是否需要角色,或者这个属性或行为是属于哪一个的;设计往往只要先满足目前的需求,并保留一定的可扩展性即可;因为很多时候即使我们把很多行为全部放在一个领域对象中而不是分散到各个角色中也不会像想象中的那么难维护;角色只是帮助我们更好的理解场景的交互者,是属于领域模型分析阶段需要用到的概念,而在设计实现阶段,可以采用折中的方案;
 回复 引用 查看   

#24楼 2011-08-31 14:06 yingzhilian      
看了你的文章感觉越陷越深了
 回复 引用 查看   

#25楼 2011-09-04 20:29 theitcn      
属性如果是只读的那MVC好像就不能自动创建对象了。MVC不能像私有对象中写值。
 回复 引用 查看   

#26楼[楼主] 2011-09-10 10:41 netfocus      
设计对象时不要被技术牵着头走。
 回复 引用 查看   

#27楼[楼主] 2011-09-10 10:42 netfocus      
如果业务需要你属性只读,但是MVC需要你不只读,因为可能需要帮你自己ORM映射,你会让业务妥协于技术吗?
 回复 引用 查看   

#28楼 2011-11-02 22:31 Yarkin      
本人菜鸟,看过ddd可是不能很好的应用到自己的系统设计和开发中,在此请问各位大侠可有相关资料?深度鞠躬
 回复 引用 查看