通过学习以上这些知识,让我对面向对象的分析、设计、实现有了一些新的认识。
业务模型建好了该如何通过面向对象的分析与设计方法来进行对象建模呢 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借阅者Borrower2被借的图书BorrowedBook3图书馆。那么这三个角色的扮演者对象是谁呢其实这是问题的关键1是谁扮演了借阅者这个角色很多人认为是走进图书馆的那个人其实不是。 人所持的图书卡对应的那个人才是真正的借阅者角色的扮演者试想张三用李四的图书卡借书借书的是谁应该是李四此时相当于李四被张三操控了而已当然这里假设图书馆不会对持卡人和卡的真正拥有者进行身份核对。所以借阅者角色的扮演者应该是借书卡对应的帐号借书卡帐号本质上是某个人在图书馆里系统中的镜像。那么图书卡帐号和借阅者角色有什么区别图书卡帐号是一个普通的领域对象只包含一些核心的基本的属性如AccountNumberOwner等但是Borrower角色则具有借书还书的行为2是谁扮演了被借的书这个角色这个问题比较好理解肯定是图书了。那图书和被借的图书有什么区别吗大家都知道图书是指还没被借走的还是放在书架上的书本而被借的书则包含了更多的含义比如被谁借的什么时候借的等等3为什么图书馆也是一个角色图书馆只是一个地点它不管有没有参与到借书场景中都叫图书馆并且它的属性也不会因为参与到场景中而改变。没错但是他确实是一个角色只不过它比较特殊因为在参与到借书场景时它是“本色演出”即它本身就是一个角色举两个其他的例子你可能就好理解一点了比如教室上课时是课堂考试时是考场比如土地建造房子时是工地种植粮食时是田地是有可能增加依赖场景的行为和属性的。有了场景和角色的之后我们就可以写出角色在场景中交互的代码了。我们此时完全不用去考虑对象如何设计更不用考虑如何存储之类的技术性东西。因为我们现在已经清晰的分析清楚1场景参与者2参与者“做什么”代码如下应该比较好懂/// summary/// 借阅者角色定义/// /summarypublic interface IBorrower : IRoleUniqueId{IEnumerableIBorrowedBook BorrowedBooks { get; } //借了哪些书void BorrowBook(Book book);//借书行为Book ReturnBook(UniqueId bookId);//还书行为}/// summary/// 图书馆角色定义/// /summarypublic interface ILibrary : IRoleUniqueId{IEnumerableBook Books { get; }//总共有哪些书Book TakeBook(UniqueId bookId);//书的出库void PutBook(Book book);//书的入库}/// summary/// 被借的书角色定义/// /summarypublic interface IBorrowedBook : IRoleUniqueId{Book Book { get; } //书DateTime BorrowedTime { get; }//被借时间}/// summary/// 借书场景/// /summarypublic class BorrowBooksContext{private ILibrary library;//场景参与者角色1图书馆角色private IBorrower borrower;//借书参与者角色2借阅者角色public BorrowBooksContext(ILibrary library, IBorrower borrower){this.library library;this.borrower borrower;}/// summary/// 启动借书场景各个场景参与者开始进行交互/// /summarypublic void Interaction(IEnumerableUniqueId bookIds){foreach (var bookId in bookIds){borrower.BorrowBook(library.TakeBook(bookId));//}}}/// summary/// 还书场景/// /summarypublic class ReturnBooksContext{private ILibrary library;private IBorrower borrower;public ReturnBooksContext(ILibrary library, IBorrower borrower){this.library library;this.borrower borrower;}public void Interaction(IEnumerableUniqueId bookIds){foreach (var bookId in bookIds){library.PutBook(borrower.ReturnBook(bookId));}}}接下来考虑角色扮演者如何设计与实现角色扮演者就是DDD中的领域对象在这个例子中主要有借书卡帐号LibraryAccount、书本Book、图书馆Library下面是这几个实体类的实现public class LibraryAccount : ObjectUniqueId{#region Constructorspublic LibraryAccount(LibraryAccountState state) : this(new UniqueId(), state){}public LibraryAccount(UniqueId id, LibraryAccountState state) : base(id, state){}#endregionpublic string Number { get; private set; }public string OwnerName { get; private set; }}public class Book : ObjectUniqueId{#region Constructorspublic Book(BookState state) : this(new UniqueId(), state){}public Book(UniqueId id, BookState state) : base(id, state){}#endregionpublic string BookName { get; private set; }public string Author { get; private set; }public string Publisher { get; private set; }public string ISBN { get; private set; }public string Description { get; private set; }}public class Library : ObjectUniqueId, ILibrary{private ListBook books new ListBook();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 ListBook(state.Books);}}[Mannual]public IEnumerableBook 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.AddLibrary(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.AddBook(book1);Repository.AddBook(book2);Repository.AddBook(book3);Repository.AddBook(book4);Repository.AddBook(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.AddLibraryAccount(libraryAccount);//创建借书场景并进行场景交互new BorrowBooksContext(library.ActAsILibrary(),libraryAccount.ActAsIBorrower()).Interaction(new ListUniqueId { book1.Id, book2.Id });//创建还书场景并进行场景交互new ReturnBooksContext(library.ActAsILibrary(),libraryAccount.ActAsIBorrower()).Interaction(new ListUniqueId { book1.Id });