建模原语:四象图

建模原语:四象图

作者:achieveidea@gmail.com

命名:模型、结构特征、行为特征、场景(及其规约)。

释义:
模型,描述事物为一组时间函数,蕴藏了与事物相关的所有事实。
特征,从模型上剥离的一组时间函数。特征分为两大类,一类是结构特征,一类是行为特征。
场景,模型凝聚相应的特征持续一段时间,描述一段时间内与模型相关的事实。场景中隐藏的一些规则、约定,称之为场景规约。

用法:
一笔一纸,一横一竖,四象顿生。
一象画景,三象画物,物在景中。
二象画形,四象画神,形神兼备。
万象入画,浑然一体,一目了然。

注:第一象限画景,即描述场景;第三现象画物,即描述模型。第二现象画物之形,即描述模型的结构特征;第四象限画物之神,即描述模型的行为特征。


诸子百家:
如果你熟悉四色原型,DESC可理解为结构特征,PPT可理解为模型,Role可理解为行为特征,MI可理解为场景。
如果你熟悉DDD, VO可理解为结构特征,Entity可理解为模型,Aggregate可理解为行为特征,Service可理解为场景, Specification可以理解为场景规约。
如果你熟悉DCI,D可理解为模型,C可以理解为场景,I可以理解为行为特征。
如果你熟悉MVC, M可理解为模型,V可理解为结构特征,C可理解为行为特征。
如果你熟悉Web,HTML可理解为模型,CSS可理解为结构特征,JS可理解为行为特征,“页面”可理解为场景,HTTP可理解为场景规约, URI可理解为场景的命名。
如果你熟悉Database, Table可理解为模型,View可理解为结构特征,Trigger可理解为行为特征,“增删改查”可理解为场景,SQL(关系代数)可理解为场景规约。

如果你对上述诸子百家的解释一无所知,恭喜你!你可能将在最佳的状态下,快速掌握四象图的精髓。

为了有一个感性的认识,以电影为隐喻。Actor(演员)表示模型,Props(道具)表示结构特征,Role(角色)表示行为特征,Script(剧本)表示场景。电影《英雄》中的陈道明是模型(演员),龙袍是结构特征(道具),秦始皇是行为特征(角色),刺伤秦始皇片段是场景(剧本)。
最后也会有两个简单的代码案例(都源自Jdon道友提出的案例),让你初步感受一下四象图的作用。

开放课题:
1)描述原语的具体语言
静态语言,动态语言?命令式语言,声明式语言?Web给我们树立了一个极好的榜样,模型使用HTML描述,结构特征使用CSS语言描述,行为特征使用JavaScript描述,场景规约使用HTTP描述,场景使用URI命名。事实上Android UI就汲取了Web的灵感,将UI的模型及结构特征使用XML描述。

2)特征剥离的方法
如果特征与模型使用同一种语言(比如Java),结构特征和行为结构的剥离需要付出额外的精力,比如前者侧重“一致性”,后者侧重“交互性”。而在Web中,结构特征和行为特征的剥离易如反掌。一致性要求画面在一段时间呈现相同的风格,这将给用户良好的界面体验的必要基础。CSS的设计者之一Bert Bos,解释了为什么CSS高度强调一致性。
Why “variables” in CSS are harmful?

3)特征凝聚的方法
模型凝聚特征,最简单的方法就是赋值。当模型具有复杂的结构特征时,可以使用“结构型”模式进行凝聚;当模型具有复杂的行为特征时,可以使用“行为型”模式进行凝聚。 凝聚特征的方法远不止赋值和GoF模式,比如JQuery中的selector、 Android中的R.java等等。

4)语言引擎的设计与实现
在不同的“系统”或“层次”之间,可能需要引入“语言引擎”这个概念,语言引擎的工作主要是协调两个说不同“语言”的“系统”之间的信息交流。比如Web应用的前端客户端说的是JavaScript语言,后端服务器使用是其它的语言(比如Java, PHP等)。Ajax(Asynchronous JavaScript and XML,由Jesse James Gaiiett发明)引擎负责两者的信息交流。Ajax的本意是通过JavaScript启动引擎,发送Request,异步接收以XML的数据格式返回的Response。现在XML已逐渐被JSON取而代之,因为相对于XML, JavaScript天生对JSON非常熟悉,是自家兄弟。

这里我修改Ajax的含义使其具有普适的意义。A = asynchronous, J = jabber, A = And, X = X。A 表示异步(同步是异步的特殊状态),Jabber的含义是“快而含糊不清的话”,X代表是任意对话者将的语言。

当某个层次或系统说的语言(Jabber),对于另一个层次或系统(使用X语言)而言可能就是“快而含糊不清的话(Jabber)”(我们多数人听老外讲话不就如此?老外听我们说话亦然!),此时就需要一个“语言引擎”进行翻译,使两者流畅地交流。语言引擎有两个工作模式:异步模式和同步模式。

对于Web UI来说,JavaScript为了自己的话能够被服务器(比如使用Java编写的服务器)听懂,使用Ajax语言引擎告知自服务器自己说了啥,要做什么。

对于Web Server(比如Java编写的服务器),为了Database能够听懂自己的话,知道自己想要些什么,做些什么,也需要构造一个Ajax引擎。语言引擎如果足够智能,可自动切换工作模式。比如在同步工作模式下,与SQL数据库建立会话;在异步工作模式,与NoSQL建立会话。

语言引擎的设计者需要具备精通数据库语言和服务器使用的语言,才可能建立通用的语言引擎。这个有能力的人可以考虑怎么实现,将其开源奉献给社区。 我目前是不知道啦。语言引擎不是Object/Relation Mapping,多多借鉴Asynchronous JavaScript and XML才是正道。

5)架构风格的设计与实现
Roy Thomas Fielding的博士论文《架构风格与基于网络的软件架构设计》是一篇极好的文章。李锟、廖志刚、刘丹、杨光的翻译也非常到位,感谢他们无私的奉献。我提出四象图的灵感部分(模型)源自于这篇论文对“资源”的定义,还有一部分灵感(特征)源于曾经对系统“特征向量空间”的痴迷。

Fielding在文中提及,“更精确地说,资源R是一个随时间变化的成员函数MR(t),该函数将时间t映射到等价的一个实体或值的集合,集合中的值可能是资源的表述和/或资源的标识符。”“REST对于信息的核心抽象是资源。任何能够被命名的信息都能够作为一个资源。”

在四象图中,Resource犹如“凝聚特征的模型”; Representational State表示“模型进入的场景”; State Transfer Protocol表示“场景的规约”;URI则是对“场景的命名”,其应绑定恰当的语义。

四象图与REST描述具有相似的“画面感”。 资源的表述性状态是一个持续一段时间的画面,状态转移协议切换各个画面。模型进入场景持续一段时间的画面,场景规约切换各个画面。两者之所以,具有相似画面感,是因为模型与资源的定义都是时间函数,是一个随着时间变化的概念。

体验与感受REST架构风格、四色原型、GoF设计模式,想象与其提出者进行交流,聆听、汲取其智慧,是我提出“四象图”的源泉。Web是一个成功的REST架构,运行了几十年。碰触Web,与接触TCP/IP协议栈、信号与系统、香农的信息论、机器学习一样,都令我感到震撼,开阔了原有的视野。

架构风格的设计与实现,我也是个在不断学习的初学者。之所以列出这个课题,是因为我觉得其非常重要,不可或缺。

6)程序之道:象数理
Niklaus Wirth提出公式“程序 = 数据结构 + 算法”,我认为这个公式少了一个东西,必须显现出来,即“模式”。古人有“象数理”之说,“象”在程序中抽象为“数据结构”,“数”抽象为“算法”,“理”抽象为“模式”。

“象”可以描述变量,过程,对象,函数,进程,事实,……,基于不同的“象”(数据结构),我们需要思考相应的“数”(算法),发现“象数”之后的“理”。这是无止境开放的课题,我们可能永远都无法找到永恒的真理。

建模原语和程序之道的来龙去脉,可以参考帖子《领域驱动设计之我见》《Hello, World! 我心中的道》,但那两个帖子充斥着混乱和错误,有时间可以了解一下,没有时间,看这个帖子就行了。这些是我提炼、加工后的结果。

提出“六大开放课题”,因为其与“四象图”在虚拟世界中的落实息息相关。有空我会写一些特征凝聚的代码例子,作为参考。uda1341的设计的作品完成差不多了,可让他普及一下程序之道这个课题出现的新方法论:Fact-Oriented Programming。

希望有更多的人,了解和使用四象图,并对随之提出的“六大课题”感兴趣,去研究和解决他们。

欢迎转载此文。最好注明原出处。
achieveidea@gmail.com

示例:

代码案例【1】电脑维修
四象图的模型:电脑


class Computer {
private String username;
private String boughtTime;
private String price;
private String brand;
private ComputerConfiguration cc;

public Computer(String username, String boughtTime, String price,
String brand, ComputerConfiguration cc) {
super();
this.username = username;
this.boughtTime = boughtTime;
this.price = price;
this.brand = brand;
this.cc = cc;
}

// getter/setter
}

四象图的结构特征:电脑配置


class ComputerConfiguration {
private String cpu;
private String keyboard;
private String hardDisk;
private String memory;
private String motherBoard;
private String monitor;
// getter/setter
}

四象图的行为特征:维修中的电脑


class ComputerInRepairing {
private String reason; // 哪里坏了
private String orderNo;
// 订单编号
private Date startTime;
// 送来修理的时间
private Date endTime;
// 修理完的时间
private String chargedPrice;
// 收费价格
private String repairer;
// 修理人
private Computer computer;

public ComputerInRepairing(Computer computer) {
this.computer = computer;
this.startTime = new Date();
}
// getter/setter
}


四象图的场景规约:收费策略


class ChargeStrategy {
public String repairHardDisk() {
return "50$";
}

public String repairMotherBoad() {
return
"20$";
}
}

四象图的场景规约:场景


class RepairComputer {
private ChargeStrategy strategy; // 收费策略

public void repair(ComputerInRepairing c) {
// repaire the computer
// charge to customer
}
}

[该贴被jdon007于2011-08-27 14:16修改过]

代码案例【2】图书馆借阅图书

四象图的模型之一:书


public class Book {
private String serialNo;
private String name;
private BookCounter counter;

public Book(String name, String serialNo, BookCounter counter) {
this.name = name;
this.serialNo = serialNo;
this.counter = counter;
}

public String getSerialNo() {
return serialNo;
}

public String getName() {
return this.name;
}

public boolean hasStock() {
return counter.hasStock();
}

public int getCount() {
return counter.getCount();
}

public int incrementCount() {
return counter.incrementCount();
}

public int decrementCount() {
return counter.decrementCount();
}
}

从“一致性”的角度,将“模型”的“结构特征”剥离出来。
四象图的结构特征:书的计数器


public class BookCounter {
private int total; // total
private int count;
// count in stock

public BookCounter(int total) {
this.total = total;
this.count = total;
}

public int getTotal() {
return total;
}

public int getCount() {
return count;
}

public boolean hasStock() {
return count == 0 ? false : true;
}

public int incrementCount() {
return count++;
}

public int decrementCount() {
return count--;
}
}

从“交互性”的角度,将“模型”的“行为特征”剥离出来。
四象图的行为特征:被借/已借的书


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

public BorrowedBook(Book book) {
this.book = book;
}

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 setBorrowedTime(Date borrowedTime) { this.borrowedTime = borrowedTime; }
public void setReturnedTime(Date returnedTime) { this.returnedTime = returnedTime; }
}


四象图的模型之二:账号


public class Account {
private String username;
private AccountType accountType;
private List<BorrowedBook> borrowedBooks;

protected Account(String username) {
this.username = username;
this.borrowedBooks = new ArrayList<BorrowedBook>();
}

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

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

public List<BorrowedBook> borrowBooks(List<Book> books) {
for (Book book : books) {
borrowedBooks.add(borrowBook(book));
}
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(returnBorrowedBook(borrowedBook));
}
}
return books;
}

public AccountType getAccountType() {
return accountType;
}

public List<BorrowedBook> getBorrowedBooks() {
return borrowedBooks;
}
}


从“一致性”的角度,将“模型”的“结构特征”剥离出来。
四象图的结构特征:账号类型


public enum AccountType {
TEARCHER, STUDENT;
}

为了保证账号的唯一性,使用单例工厂创建账号。


public class AccountFactory {
private static Map<String, Account> accounts = new HashMap<String, Account>();

private AccountFactory() {
}

public static synchronized Account getAccount(String username) {
if (!accounts.containsKey(username)) {
accounts.put(username, new Account(username));
}
return accounts.get(username);
}
}


四象图的模型之三: 图书馆


public class Library {
public static Map<String, Book> books = new HashMap<String, Book>();

public static Map<String, Book> getBooks() {
return books;
}

public static void setBooks(Map<String, Book> books) {
Library.books = books;
}

public static Book takeBook(String serialNo) {
Book book = books.get(serialNo);
if (book.hasStock()) {
book.decrementCount();
return book;
}
return null;
}

public static void putBook(String serialNo) {
Book book = books.get(serialNo);
book.incrementCount();
books.put(serialNo, book);
}

public static int getCount(String bookName) {
int count = 0;
Collection<Book> _books = books.values();
for (Book book : _books) {
if (book.getName().equalsIgnoreCase(bookName)) {
count = book.getCount();
break;
}
}
return count;
}
}

代码案例【2】图书馆借阅图书

四象图的场景之一:借书


public class BorrowBookContext {
public List<BorrowedBook> borrowBooks(Account account, List<String> serialNos) {
List<BorrowedBook> borrowedBooks = new ArrayList<BorrowedBook>();
for (String serialNo : serialNos) {
borrowedBooks.add(borrowBook(account, serialNo));
}
return borrowedBooks;
}

public BorrowedBook borrowBook(Account account, String serialNo) {
Book book = Library.takeBook(serialNo);
return account.borrowBook(book);
}
}


四象图的场景之二:还书


public class ReturnBookContext {
public void returnBorrowedBooks(List<BorrowedBook> borrowedBooks) {
for (BorrowedBook borrowedBook : borrowedBooks) {
returnBorrowedBook(borrowedBook);
}
}

public void returnBorrowedBook(BorrowedBook borrowedBook) {
Account borrower = AccountFactory.getAccount(borrowedBook.getUsername());
Book book = borrower.returnBorrowedBook(borrowedBook);
Library.putBook(book.getSerialNo());
}
}

四象图的场景规约:借阅规则

简单起见,两个场景没有使用借阅规则。更完善的实现,借阅规则应作为场景的前置条件和交互过程中的约束条件。

场景规约是领域的灵魂,模型是领域的躯体。场景规约代表业务规则。在领域的各个场景中,灵与肉本应合为一体。

没有场景规约(业务规则)的模型,在场景中犹如行尸走肉。考虑到游戏《植物大战僵尸》的火爆,这里且让模型的灵魂脱壳。^_^


public class BorrowingTerms {
private static final int LIMIT_OF_BOOKS = 5;
private static final long LIMIT_OF_DAYS = 30;
private static final int LIMIT_OF_STOCK = 1;
private static final double UNIT_OF_PENALTY = 0.1;

// 账号是否借满书
public static boolean checkAccount(Account account) {
int borrowedBooks;
borrowedBooks = account.getBorrowedBooks().size();
return (borrowedBooks > LIMIT_OF_BOOKS) ? false : true;
}

// 书是否超期
public static boolean checkBorrowedBook(BorrowedBook borrowedBook) {
return (exceededDays(borrowedBook) == 0) ? true : false;
}

// 图书馆是否还有额外的库存
public static boolean checkLibrary(String bookName) {
int stockBooks = Library.getCount(bookName);
return (stockBooks > LIMIT_OF_STOCK) ? true : false;
}

// 超期的天数
public static long exceededDays(BorrowedBook borrowedBook) {
long borrowedDays, exceededDays;
borrowedDays = (borrowedBook.getReturnedTime().getTime() - borrowedBook
.getBorrowedTime().getTime())
/ (24 * 60 * 60 * 1000);
exceededDays = (borrowedDays - LIMIT_OF_DAYS);
return (exceededDays > 0) ? exceededDays : 0;
}

// 一本书超期的罚款金额,1本书1天0.1元
public static double getPenalty(BorrowedBook borrowedBook) {
return exceededDays(borrowedBook) * UNIT_OF_PENALTY;
}

// 多本书超期的罚款金额
public double getPenalty(List<BorrowedBook> borrowedBooks) {
double penalty = 0;
for (BorrowedBook borrowedBook : borrowedBooks) {
if (BorrowingTerms.checkBorrowedBook(borrowedBook)) {
penalty += BorrowingTerms.getPenalty(borrowedBook);
}
}
return penalty;
}

// 此账号是否可以借此书
public static boolean canBorrowBook(Account account, String bookName) {
if (!BorrowingTerms.checkAccount(account))
return false;
if (!BorrowingTerms.checkLibrary(bookName))
return false;
for (BorrowedBook borrowedBook : account.getBorrowedBooks()) {
if (!BorrowingTerms.checkBorrowedBook(borrowedBook))
return false;
}
return true;
}
}

// 测试用例


public class UseCase {
public static void showLibarayInfo() {
Collection<Book> books = Library.getBooks().values();
System.out.println("----------- Libaray----------------");
for (Book book : books) {
System.out.println(
"{" + book.getName() + ": " + book.getCount() +"}");
}
System.out.println(
"-----------------------------------");
}

public static void showAccountInfo(Account account) {
List<BorrowedBook> books = account.getBorrowedBooks();
System.out.println(
"----------- Account----------------");
for (BorrowedBook book: books) {
System.out.println(
"{"+book.getBook().getName()+"}");
}
System.out.println(
"-----------------------------------");
}

public static void initLibaray() {
Map<String, Book> books = new HashMap<String, Book>();
books.put(
"001", new Book("Jdon Framework", "001", new BookCounter(10)));
books.put(
"002", new Book("Spring Framework", "002", new BookCounter(20)));
books.put(
"003", new Book("Guice Framework", "003", new BookCounter(30)));
Library.setBooks(books);
}

public static void main(String[] args) {
initLibaray();

// 0 图书馆里找书
List<String> serialNos = new ArrayList<String>();
serialNos.add(
"001");
serialNos.add(
"002");
serialNos.add(
"003");

// 1 持卡借书
Account jobs = AccountFactory.getAccount(
"Steve Jobs");

// 1.1) 借书之前
System.out.println(
"【0】 Before Borrowing Books");
showLibarayInfo();
showAccountInfo(jobs);

// 1.2)借书
BorrowBookContext borrowService = new BorrowBookContext();
List<BorrowedBook> borrowedBooks = borrowService.borrowBooks(jobs, serialNos);
// 1.2)借书之后
System.out.println(
"【1】After Borrowing Books");
showLibarayInfo();
showAccountInfo(jobs);

// 2 阅读借来的书,读完时就该还书了。
System.out.println(
"\n【2】 ******** Steve Jobs is reading books! ********\n" );

// 3 无卡还书
// 3.1) 还书之前
System.out.println(
"【3】 Before Returning Books");
showLibarayInfo();
showAccountInfo(jobs);

// 3.2) 还书,
ReturnBookContext returnService = new ReturnBookContext();
returnService.returnBorrowedBooks(borrowedBooks);

// 3.3) 还书之后
System.out.println(
"【4】After Returning Books");
showLibarayInfo();
showAccountInfo(jobs);
}
}

// 运行结果
【0】 Before Borrowing Books
----------- Libaray----------------
{Jdon Framework: 10}
{Spring Framework: 20}
{Guice Framework: 30}
-----------------------------------
----------- Account----------------
-----------------------------------
【1】After Borrowing Books
----------- Libaray----------------
{Jdon Framework: 9}
{Spring Framework: 19}
{Guice Framework: 29}
-----------------------------------
----------- Account----------------
{Jdon Framework}
{Spring Framework}
{Guice Framework}
-----------------------------------

【2】 ******** Steve Jobs is reading books! ********

【3】 Before Returning Books
----------- Libaray----------------
{Jdon Framework: 9}
{Spring Framework: 19}
{Guice Framework: 29}
-----------------------------------
----------- Account----------------
{Jdon Framework}
{Spring Framework}
{Guice Framework}
-----------------------------------
【4】After Returning Books
----------- Libaray----------------
{Jdon Framework: 10}
{Spring Framework: 20}
{Guice Framework: 30}
-----------------------------------
----------- Account----------------
-----------------------------------

// 附件,文档和源码。

attachment:


notes.doc
domain.rar

007,可以不可以这样表达:



我认为模型可能还具备一个本质的结构和行为,就像数学中的公理,基于这个公理在各种场景中演化出定理。

2011年08月28日 16:45 "@oojdon"的内容
可以不可以这样表达: ...

今天出去游玩了,刚回来。

我很高兴,因为我认为你已经掌握了四象图的精髓。

你的坐标图,把“时间”概念作为一维凸显出来,这是四象图的强调的一个重点。因为时间是变化的前提,且不以人之意志为转移。如果你在时间一维上再画出一个区间,随之再画出相应一个场景,可能看起来会更清晰一些,当然简洁性有所降低。

所谓公理,无非是形式化的、具有普适性的常识而已,不证自明。我说这话,不是说公理的形式化表述很简单(其形成过程一般是颇费周折的),而是说公理的特点:形式化、普适性。

四象图也许做不到这点,但至少是朝这个方向思考的。

模型具有“基本”的结构和行为(或称之为基本事实),但这个“基本点”的定义会受到我们具体目标和需求的影响。

模型的“基本事实”未必是模型的“本质”,但与公理有相通之处,在此基础之上,我们进行演绎推理,产生各种场景。

公理 != 本质,大家都认可的道理、常识,未必是模型的本质。

对于本质,我的态度与古今中外一些人的态度基本一致。

老子:道可道,非常道。
康德:用人类理性发明的语词只能谈论现象,不能谈论世界的本质。
维特根斯坦:凡不可说的,应当沉默。

我认为模型谈不上时间,而是在对象空间中谈时间(banq以前也有过这样的观点:时间和空间都是需要考虑的,但我们先放下其中一个考虑问题,然后再谈论这一个。其中谈论空间的如结构,谈论时间的如生命周期等)。

关于“本质”,我想,没有一个家伙会不联系领域来,独自创造出对象。不同领域有不同BOOK的角度,懂得从业务出发,从领域出发的,就不会产生这样的问题。即使说出本质,也是一个“局限性”的本质。

>>模型的“基本事实”未必是模型的“本质”。
<<注意模型也是我们的想象物,我认为我们一直在做的是构造一个以这些“基本事实”为本质的模型。这个本质是对模型而言,而非世界。而我们所谈模型的本质,可以说就是这些有限的事实。本质的认识来源于事实,如本质属性。

我对本质的看法是:超越人类认识的本质是不可言的。简单例子就是“世界当中的人谈世界本质”。

jdon007大牛,可不可以用Ruby再写个DCI的例子,学习学习啊。先谢过O(∩_∩)O~

2011年08月31日 07:51 "@mistbow"的内容
jdon007大牛,可不可以用Ruby再写个DCI的例子 ...

翻墙看:DCI与Rails

2011年08月31日 09:34 "@banq"的内容
翻墙看:DCI与Rails ...

文中“we don't do object oriented programming but we do class oriented programming”,看来理解DCI后,才是OO的开端。

2011年08月31日 07:51 "@mistbow"的内容
Ruby再写个DCI的例子 ...

1) http://andrzejkrzywda.com/


class Human
attr_accessor :address
end

class Address
def to_s
"City: #{@city}"
end

private

def initialize(city)
@city = city
end
end

module Policeman
def check_address(address)
# do some stuff
end
end

module Thief
end

module FakeAddress
def to_s
"City: LA"
end
end

class ArrestContext
def execute
@policeman.check_address @thief.address.to_s
end

private

def initialize(policeman, thief)
@policeman = policeman
@policeman.extend Policeman

@thief = thief
@thief.extend Thief
@thief.address.extend FakeAddress
end
end

policeman, thief = Human.new, Human.new
thief.address = Address.new 'NY'

context = ArrestContext.new policeman, thief
context.execute

2) https://github.com/anachronistic/DCI-Rails-Example

这是网络上的两个例子,你可以看看,不用翻墙。第一个例子是“警察抓小偷”,第二例子我没看。

“警察抓小偷”例子中的人扮演警察与小偷可形象说明扮演角色。不过Address扮演Fake Address,我觉得有些牵强。建议看看“The Common Sense of Object Orientated Programming”一文,Trygve Reenskaug较为详尽地阐释其提出的两大范式:MVC和DCI。

最近终于稍微有点空了,
jdon007,看了你这个帖子中提到的理论和例子,我有很多问题想确认:

1)问题一:
2011年08月31日 07:51 "@jdon007"的言论
// 3.2) 还书,
ReturnBookContext returnService = new ReturnBookContext();
returnService.returnBorrowedBooks(borrowedBooks ...

BorrowedBook是行为特征,在你的例子中,因为过于简单,borrowedBooks没有从某个持久化层获取;而真正的系统,当你要还书时,borrowedBooks肯定要从某个持久化层(比如数据库)获取的。如果要这样,你会如何做呢?如何在还书之前获取borrowedBooks?按照我的理解Book是模型,BorrowedBook是角色,即你说的行为特征,而Eric Evans的DDD理论中的Repository是针对Aggregate的;之前你说过,行为特征相当于DDD中的Aggregate,所以,是否意味着BorrowedBook应该从一个名叫BorrowedBookRespository的仓储去取呢?因为据我了解Repository都是针对Aggregate的,而据你所说,Aggregate相当于行为特征;按照这样理解的话,是不是意味着单独的Entity,即你所说的模型就无法被持久化或重建了呢?有这个疑问的原因是你并没有提到关于如何持久化/重建 模型或行为特征。我相信有很多情况模型是没有参与任何业务场景(即不需要扮演任何角色,也就是你所说的凝聚行为特征)就会被持久化或重建的;

2)问题二:
我无法理解并且感觉很别扭的一点是,为什么模型Book会知道自己还有多少本库存?即Book为什么会具有一个BookCounter?我觉得你一味的强调如何建模,而忽略了建模最重要的思想,那就是一切建模结果要能体现业务,任何属性或职责的分配要完全根据业务需要来。在我看来,Book就是Book,它怎么知道自己现在还有多少本在书架上,只有图书馆在清楚某本书目前还有多少本库存。试想一下,如果某本Book被借走了,但是Borrower却能通过这本Book知道目前图书馆还有多少本这个书的库存,那是不是有点可笑?现实生活中,如果要查询某本书在图书馆还有多少本可存,必须要通过图书馆才能查询得到;

3)问题三:
我觉得你对一件事情不太重视,那就是属性的只读问题,如果真正地在对业务进行建模,那应该深刻了解业务需求,比如被借的书(BorrowedBook)的借书人和借书时间这两个信息是随着BorrowedBook的被创建而诞生的,并且最重要的是这两个信息也从此不可能被修改,但你却给这两个信息提供了set,这意味着我可以随便随时修改这两个信息,这就会导致领域模型的业务逻辑不安全,不可控;真正的领域模型不仅能表达业务实体,业务规则,以及业务实体之间的交互,从而帮你做事情;更重要的一点是,它一定是业务安全的,即你无法利用它来做任何违反业务规定的事情,否则这样的领域模型即便设计的再好也是非常危险的;所以,我觉得你在设计模型或角色时,对于构造时初始化属性还是提供Set来初始化属性方面还需要注意,不能太随便;

4)问题四:
记得之前你最早的版本中,是有图书卡的,即Card,而现在的模型中没有了Card,取而代之的是Account,即帐号,这两者是同一个意思吗?如果是,那么Account和用户之间的关系是什么样的?是不是多对多的关系,即一个用户理论上可以使用多个Account登录,然后做借书或还书的事情;同一个Account理论上也可以被不同的用户使用,只要那些用户知道该帐号的使用密码权限;也就是说用户和Account是使用者与被使用者的关系,而这个关系正式用户和Card之间的关系;用户是系统使用者,是行为驱动者,Account是行为拥有者;我是不是可以理解为你正是由于理解了这一点才把原来的Card替换为了Account呢?

5)问题五:
记得之前你谈到过,Account即是模型,也是角色;并且你说过只有角色才可能有交互行为的,模型拥有的是非交互的行为;在你这里最新版本的图书借阅的例子中,Account拥有了借书和还书的行为,这两个是交互行为;但是你说Account是模型,而非角色,即你所说的行为特征,那是不是有点问题呢?是不是应该分开,即搞一个Account模型,它没有借书和还书的交互行为;再搞一个Borrower角色,它有借书和还书的行为;这样是不是更严谨,更符合你所提到的建模理论呢?

6)问题六:
如果真的要分行为特征或角色的话,那为什么“被借的书”(BorrowedBook)的还书时间也属于这个“行为特征”或角色呢?按照我的理解,我会觉得很奇怪,因为被借的书不应该也不需要存储还书时间这个信息,在还书场景中,没有“被借的书”这一行为特征,而只有“被还的书”这一行为特征,并且“被还的书”才应该具有还书时间这个信息,这样才对称,才准确地不多不少地符合角色所表达的含义。你是不是觉得这样会导致角色泛滥而直接把还书时间也放在BorrowedBook这个“行为特征”上呢?如果是这个原因,你能说一下何时该创建角色,创建角色的度在哪里吗?


暂时只想到这些问题了,我说话的态度可能不是太客气,请不要见怪,我没有任何针对您的意思,呵呵。
[该贴被tangxuehua于2011-09-05 22:57修改过]
[该贴被tangxuehua于2011-09-05 23:00修改过]

一、
DDD的Aggregate,“聚合”之意,可以理解为“模型凝聚行为特征”后的角色,但这很勉强,之前我说过。
DDD我看了一段时间,但现在基本上为四象图所替代,记不太清楚了。
如果没有记错,Repository应该是针对Aggregate, Entity, Value Object。
行为特征(borrowedBook),在图书管这个例子中,表达了交互的事实,“谁”何时“借(还)”了何“书”,可持久化。
模型(book),表达了基本的事实,图书馆里存放着那些书,可持久化。
结构特征(bookCounter),表达了书的一些数量信息,也可以持久化。
在我看来,场景也可以持久化,审批流就是一个例子。

三种事实的变化频率(访问或更新频率)不同,在数据库中可以设计三张表来表示。可以通过视图来提高关连查询的性能。

二、
比如,某书总共有几本,被接走了几本,剩下几本,都是数量信息,表达为结构特征。
计数器,可理解为书使用的道具,用来计量自身的一些信息,就像在炸药上安装的计时器。

三、
借书时要记录借书时间,还书时要记录还书时间,setter是需要的。业务安全属于业务规则,用场景规约来控制。模型是自由的(需要setter就setter,不需要就拿掉,不能强求都不能setter), 如果没有业务规则的约束。

四、
Account和Card都是工具,在这里可以说互为映射,前者是虚拟之物,是现实之物。软件中用户使用Account借书,现实中用户使用Card借书。至于User是否可以有多个Account或Card,取决于业务规则。

五、
Account关注的基本事实,是其借了哪些本书,还了哪些些书,borrow和return只是记录这个事实而已。
Account参与交互时,其行为特征,在模型身上已经定义了,无需再定义。
除非Account有其他用途(存在截然不同的应用场景),才有必要调整其应该关注的事实。
比如Account也可以用来借钱,那么其应该关注的基本事实,即Account是谁使用的等信息即可。
Library的在此例子中与Account相似,基本事实已经包含了交互性质的事实。

现在HTML5(模型)可以也具有一些JS(刻画行为特征,交互性)的能力,这应该是更合理的设计。
从Web,而不是DDD、DCI、彩色UML,来理解四象图,会容易一些。因为一年前,实际上我对Web(背后的思想)几乎是一无所知的,所以关于它的一切对我都是新鲜的,影响也更大。

六、
借书与还书,是两个相对对立的场景,有各自的行为特征(交互的事实)。
但分离这两个事实意义不大,更有意义的是关注“借与还”这一相对完整的事实。
角色或行为特征创建的角度,主要是从交互性出发。

使用DDD,DCI, 彩色UML作类比,是因为我以为,在这里这些是对大家“更友好的术语”,“更容易接近我要表达的东西”,但也有可能让读者理解起来,浅尝辄止,甚而背道而去,非我所言。

oojdon其实很好地理解了我想表达的东西。
[该贴被jdon007于2011-09-06 07:21修改过]