关于借书场景的领域建模,我从以下几个方面进行分析:
我分析一个领域模型的静态结构的思路一般是:先找出我们需要关心的对象,对于借书这个场景,我们关心的有:
1. Account(账号):Id(账号唯一标识,自动生成), Number(卡号), Owner(账号当前拥有者用户信息), BorrowedBooks(账号当前借到的书)
2. Book(书本):Id(唯一标识,自动生成),BookInfo(值对象,包含书本基本信息),Count(表示当前库存数量)
3. BorrowHistory(借书历史、借书日志):AccountId(借书账号),BookId(书本Id),Count(数量,表示借了几本),BorrowTime(借书时间)
4. BorrowedBook(借到的书):BookId(书本Id),Count(书本数量)
通过上面的分析,那么模型的静态结构就很容易画出来了。
这种分析思路是最容易的,因为我们不用考虑对象之间如何交互,我们只需要考虑场景结束后,每个对象会发生什么变化即可。所以,按照这个思路,我们得出借书这个场景发生后有以下几个对象会发生变化:
上面这3步在经典DDD中,我们通常会设计一个领域服务来完成,比如叫BorrowBookService
可以发现,其实面向过程的分析思路是一种面向结果的分析方法;我们只需要考虑一个交互过程的结果改变了哪些对象的什么状态即可,而对象之间到底如何交互的我们不用显式的建模出来;所以这种建模方法相对简单,因为我们考虑的东西比下面第这种方式要少一样东西,那就是对象之间的交互。
也许有人会说,上面的面向过程的建模思路不是真正的OO,因为对象之间没有交互,对象只是一个data,只是一个对数据的封装而已。
那么,如果要让对象之间体现出交互,那我们该如何分析呢?我觉得最关键的是要把握一点:我们分析的时候要时刻按照“谁通知谁做什么事情,或谁被通知做什么事情”这个思路来分析;
好,那按照这个思路,那么对上面的对象:Account,Book,BorrowedHistory,我们如何来分析呢?
首先假设我们已经设计好了这个软件,然后有一个界面显示在屏幕上,然后用户用它的卡号(account.Number)登陆了系统,然后用户通过查询选择了几本书,然后点击“借书”按钮。整个借书场景就是从这个“借书”按钮开始启动。另外,整个借书场景的参与者信息有:accountId,bookId,count,表示哪个账号对哪本书借了几本。
好,那么,“借书”按钮被点击后,应该有一个对象被激活,先不管该对象是什么,我们只要知道该对象会做一件事情,就是:
var borrower = repository.load<Account>(accountId); //将借书人账号load到内存 var book = repository.load<Book>(bookId); //将书本对象load到内存 borrower.BorrowBook(book, count); //启动账号的借书行为
接下来borrower.BorrowBook方法内部会发生什么呢?想想现实世界怎么发生的就知道了,你去图书馆借书,你肯定告诉管理员说“我要借这几本书”,这句话潜在的意思是,请你把这几本书借给我,谢谢,呵呵。
那就很明白了,应该有一个对象,如图书馆管理员(administrator),他有借出书(LendBook)的职责行为,那么上面的BorrowBook方法内看起来就是如下这样:
borrower.BorrowBook(book, count) { //通知图书馆管理员把指定的书借给我count本,this就是我,呵呵 administrator.LendBook(this, book, count); //管理员把书借出来后,更新账号自己的当前借到的书的信息 var borrowedBook = _borrowedBooks.SingleOrDefault(x => x.BookId == book.Id); if (borrowedBook == null) { borrowedBooks.Add(new BorrowedBook(book, count)); } else { borrowedBook.AddBookCount(count); } }
接下来我们可以考虑administrator.LendBook这个行为做了什么?
administrator.LendBook(account, book, count) { //通知书本减少其库存数量 book.DecreaseCount(count); //方法内部需要检查库存数量是否足够,如果不够需要抛异常; //这里应该要记录借书记录了,因为译本书是否被借出的衡量标准是图书馆管理员说了算的,当他 //用扫描仪对该本书进行了扫描并确认后,就表示该本书确定被借出去了,所以我们可以在这里做 //创建借书记录的逻辑。 var borrowHistory = new BorrowHistory(account, book, count, DateTime.Now); //下面理想情况下我们不希望在这里保存borrowHistory,但是如果光是new一个BorrowHistory对象出来, //是没办法被持久化出来的,必须通过某种方式通知框架保存new出来的这个对象, //如果用经典的ddd,那如何保存borrowHistory呢?后面我会谈到一些关于这个的思考。 }
好,上面的分析我想应该很清晰地表达了对象之间如何交互,从而完成整个借书场景。但是,上面提到“借书”按钮被点击后,应该有一个对象被激活。那么这个对象会是什么呢?
我觉得你可以设计一个BorrowBookService领域服务,也可以设计一个BorrowBookContext场景类,它有一个Interaction(交互的意思)方法:
borrowBookContext.Interaction(Guid accountId, Guid bookId, int count) { var borrower = repository.load<Account>(accountId); //将借书人账号load到内存 var book = repository.load<Book>(bookId); //将书本对象load到内存 borrower.BorrowBook(book, count); //启动账号的借书行为 }
所以,整个交互的过程就是:
从上可以明显的看出,每一个交互都是对象a通知对象b做什么,这是关键;上面的分析和代码充分体现了“对象交互”,也许这样的代码才是更OO吧,呵呵。
为了大家更好的理解这种OO的方式,我特地写了一个完整的例子。源代码下载地址:http://files.cnblogs.com/netfocus/BookLibraryExample.rar