领域驱动设计之我见

11-01-29 jdon007
                   

开这个新帖子的诱因,是《寻找答案之DDD》的发贴者要我写的图书管理系统的原型代码。 因为这个案例足够小,而且在学校,我们一般都有借书的经历,对图书管理系统所要表达的领域有充分的认识,可以判断设计出来的领域模型是否优雅,是否接近客观领域的本质。

《寻找答案之DDD》中引用了敏捷之道的张逸先生使用DDD方法设计的图书管理系统的领域模型,张逸先生是位验丰富的程序员,以前看过他的一篇文章《什么是好的代码》,里头有一个观点,好的代码中每个方法最好不要超过5行。这个量化的指标,要完成挺难的(完全做到可能也没有必要),我只能尽力而为。MVC和DCI的提出者Trygve Reenskaug提倡用可读性极佳的代码直接捕获用户心中的模型,我采纳了这个观点,同时在编写原型时借鉴了四色原型及DCI的部分思想。

这里先陈述我的一些思考和观点,可能与一些经典著的定义有所出入,随后再将代码贴出来。

1)领域是客观的,不以人的意志为转移,但人之于领域具有能动性,可以认识和改造它。

2)领域模型是主观的,体现了程序员对领域的认识,是程序员心中对领域的素描。

3)用户需求是主观的,体现了用户对领域的认识,是用户心中对领域的素描。

4)领域模型需要捕捉和包容用户需求。领域模型与用户需求的关系十分重要,下面展开来讲。

“用户需求”是对领域的“素描”,用户的需求来自对领域的“诉求”,这些诉求往往是深刻的,因为其来源于用户对领域长期观察和使用的经验,比起我们程序员,一般更完整、更真实地接近领域的本质。我们对“用户需求进行素描”,就是“借鉴用户的宝贵经验”,可以更快、更好地素描客观领域,这可以说是一条认识(未知)领域的捷径。但是当用户需求不明朗或不清晰时,我们需要超越“用户需求”,对领域进行深入的摸索,去寻求更清晰的视角,对领域进行刻画。

此外,“用户需求”不能等同于“用户”,捕捉“用户心中的模型”也不能等同于“以用户为核心模型”,这是不同的概念,不能忽略其差异。《老子》书中有个观点:有之以为利,无之以为用。在这里,有之利,即建立领域模型;无之用,即包容用户需求。举些例子,一个杯子要装满一杯水,我们在制作杯子时,制作的是空杯子,即要把水倒出来,之后才能装下水;再比如,一座房子要住人,我们在建造房子时,建造的房子是空的,唯有空的才能容乃人的居住。我们建立领域模型时也要将用户置于模型之外,这样才能包容用户的需求。

在图书馆管理系统的领域,借书人(Reader)即用户,建模时应该将其置之于外(借书人在系统中的镜像可以考虑放在应用层实现),借书卡(Card)是该领域一个重要的模型,借书人可以使用它进行借书。我的设计与张逸先生的设计差异很大,考虑到其具有丰富的经验,所以我不能断言我的设计是否更接近于领域,下面我将代码原型贴出来,因为只是用来示意和讨论的,所以有些实现做了简化。欢迎各路神仙进行评断、深入探讨,如果张逸先生本人也可以来到这里参与讨论,最好不过了。

推荐其博客,内容极其丰富。

http://www.agiledon.com/post/2010/06/107.html

                   

20
jdon007
2011-01-29 19:17

// 书的一些详细信息,在四色原型中是DESC

package domain;

public class BookDetail {
	private String author;
	private String publisher;

	public BookDetail(String author, String publisher) {		
		this.author = author;
		this.publisher = publisher;
	}

	public String getAuthor() { return author; }
	public String getPublisher() { return publisher; }

	public void setAuthor(String author) { this.author = author; }
	public void setPublisher(String publisher) { this.publisher = publisher; }
}
<p>

// 书本身,在四色原型中是PPT之Thing

package domain;

public class Book {
	protected int count;	
	private String name; 
	private String serialNo;
	private BookDetail detail;
	
	public Book(int count, String name, String serialNo, BookDetail detail) {		
		this.count = count;
		this.detail = detail;
		this.name = name;
		this.serialNo = serialNo;
	}

	public String getSerialNo() { return serialNo;	}
	public String getName() { return name; }
	public int getCount() {	return count; }
	public BookDetail getDetail() {	return detail; }

	public void setSerialNo(String serialNo) { this.serialNo = serialNo; }
	public void setName(String name) { this.name = name; }
	public void setDetail(BookDetail detail) { this.detail = detail; }
	public void setCount(int count) { this.count = count; }
}
<p>

jdon007
2011-01-29 19:21

// 所借之书,在四色原型中是Role

package domain;

import java.util.Date;

public class BorrowedBook {
	private String username;
	private Book book;	
	private Date borrowedTime;
	private Date returnedTime;

	public BorrowedBook(Book book) {
		this.book = book;
		this.borrowedTime = new Date();
		this.returnedTime = new Date();
	}
	
	@Override
	public String toString() {		
		return book.getName();
	}

	public String getUsername() { return username;}
	public Book getBook() { return book; }
	public Date getBorrowedTime() { return borrowedTime; }
	public Date getReturnedTime() {	return returnedTime; }

	public void setUsername(String username) { this.username = username; }
	public void setBook(Book book) { this.book = book; }
	public void setBorrowedTime(Date borrowedTime) { this.borrowedTime = borrowedTime; }
	public void setReturnedTime(Date returnedTime) { this.returnedTime = returnedTime; }
}
<p>

jdon007
2011-01-29 19:26

// 借书卡,在四色原型中是Role,也是PPT之Thing

package domain;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Card {
	private String username;
	private CardType cardType;
	private List<BorrowedBook> borrowedBooks = Collections
			.synchronizedList(new ArrayList<BorrowedBook>());

	protected Card(String username) {
		this.username = username;
	}	

	public BorrowedBook borrowBook(Book book) {
		BorrowedBook borrowedBook = new BorrowedBook(book);
		borrowedBook.setUsername(username); 	
		borrowedBooks.add(borrowedBook);
		return borrowedBook;
	}

	public Book returnBorrowedBook(BorrowedBook borrowedBook) {
		Book book = borrowedBook.getBook();
		borrowedBooks.remove(borrowedBook);
		return book;
	}

	public List<BorrowedBook> borrowBooks(List<Book> books) {
		BorrowedBook borrowedBook;
		for (Book book : books) {
			borrowedBook = new BorrowedBook(book);
			borrowedBook.setUsername(username);		
			borrowedBooks.add(borrowedBook);
		}
		return borrowedBooks;
	}

	public List<Book> returnBorrowedBooks(List<BorrowedBook> borrowedBooks) {
		List<Book> books = null;
		if (this.borrowedBooks.removeAll(borrowedBooks)) {
			books = new ArrayList<Book>();
			for (BorrowedBook borrowedBook : borrowedBooks) {
				books.add(borrowedBook.getBook());
			}
		}
		return books;
	}	

	public void showInfo() {
		StringBuffer buf = new StringBuffer();
		buf.append(username + " borrows:");

		if (borrowedBooks.isEmpty()) {
			buf.append(" nothing.");
			System.out.println(buf.toString());
			return;
		}
		for (BorrowedBook borrowedBook : borrowedBooks) {
			buf.append(" " + borrowedBook.getBook().getName());
		}
		System.out.println(buf.toString()+".");
	}

	public String getUsername() { return username; }
	public CardType getCardType() {	return cardType; }
	public List<BorrowedBook> getBorrowedBooks() { return borrowedBooks; }
	
	public void setUsername(String username) { this.username = username; }
	public void setCardType(CardType cardType) { this.cardType = cardType; }
	public void setBorrowedBooks(List<BorrowedBook> borrowedBooks) { this.borrowedBooks = borrowedBooks; }
}
<p>

jdon007
2011-01-29 19:29

// 借书卡的类型,在四色原型中是DESC

package domain;

public enum CardType {
	TEARCHER, STUDENT;
}
<p>

// 创建借书卡,保证其唯一性。

package domain;

import java.util.HashMap;
import java.util.Map;

public class CardFactory {
	private static Map<String, Card> cards = new HashMap<String, Card>();

	private CardFactory() {
	}

	public static synchronized Card getCard(String username) {
		if (!cards.containsKey(username)) {
			cards.put(username, new Card(username));
		}
		return cards.get(username);
	}
}
<p>

22Go 1 2 3 4 ... 22 下一页