随笔-14  评论-25  文章-1  trackbacks-0
  2014年6月21日
在一个项目里面有这么一个技术需求:
1.集合中元素个数,10M
2.根据上限和下限从一个Set中过滤出满足要求的元素集合.

实际这个是个很典型的技术要求, 之前的项目也遇见过,但是因为当时的类库不多, 都是直接手写实现的. 方式基本等同于第一个方式.

在这个过程中, 我写了四个方式, 基本记录到下面.
第一个方式:对Set进行迭代器遍历, 判断每个元素是否都在上限和下限范围中.如果满足则添加到结果集合中, 最后返回结果集合.
            测试效果:集合大小100K, 运算时间 3000ms+
过滤部分的逻辑如下:
 1     void filerSet(Set<BigDecimal> targetSet, String lower, String higher) {
 2         BigDecimal bdLower = new BigDecimal(Double.parseDouble(lower));
 3         BigDecimal bdHigher = new BigDecimal(Double.parseDouble(higher));
 4 
 5         Set<BigDecimal> returnSet = new HashSet<BigDecimal>();
 6         for (BigDecimal object : targetSet) {
 7             if (isInRange(object, bdLower, bdHigher)) {
 8                 returnSet.add(object);
 9             }
10         }
11     }
12 
13     private boolean isInRange(BigDecimal object, BigDecimal bdLower,
14             BigDecimal bdHigher) {
15         return object.compareTo(bdLower) >= 0
16                 && object.compareTo(bdHigher) <= 0;
17     }
第二个方式: 借助TreeSet, 原始集合进行排序, 然后直接subset.
            测试效果: 集合大小10M, 运算时间: 12000ms+(获得TreeSet) , 200ms(获得结果)
过滤部分的逻辑如下(非常繁琐):
  1     Set<BigDecimal> getSubSet(TreeSet<BigDecimal> targetSet, String lower,
  2             String higher) {
  3 
  4         BigDecimal bdLower = new BigDecimal(Double.parseDouble(lower));
  5         BigDecimal bdHigher = new BigDecimal(Double.parseDouble(higher));
  6 
  7         if ((bdHigher.compareTo(targetSet.first()) == -1)
  8                 || (bdLower.compareTo(targetSet.last()) == 1)) {
  9             return null;
 10         }
 11 
 12         boolean hasLower = targetSet.contains(bdLower);
 13         boolean hasHigher = targetSet.contains(bdHigher);
 14         if (hasLower) {
 15             if (hasHigher) {
 16                 System.out.println("get start:" + bdLower);
 17                 System.out.println("get end:" + bdHigher);
 18                 return targetSet.subSet(bdLower, true, bdHigher, true);
 19             } else {
 20                 BigDecimal newEnd = null;
 21                 System.out.println("get start:" + bdLower);
 22                 SortedSet<BigDecimal> returnSet = null;
 23                 if (bdHigher.compareTo(targetSet.last()) != -1) {
 24                     newEnd = targetSet.last();
 25                 } else {
 26                     SortedSet<BigDecimal> newTargetSet = targetSet
 27                             .tailSet(bdLower);
 28                     for (BigDecimal object : newTargetSet) {
 29                         if (object.compareTo(bdHigher) == 1) {
 30                             newEnd = object;
 31                             break;
 32                         } else if (object.compareTo(bdHigher) == 0) {
 33                             newEnd = object;
 34                             break;
 35                         }
 36                     }
 37                 }
 38                 returnSet = targetSet.subSet(bdLower, true, newEnd, true);
 39                 if (newEnd.compareTo(bdHigher) == 1) {
 40                     returnSet.remove(newEnd);
 41                 }
 42                 return returnSet;
 43             }
 44 
 45         } else {
 46             if (hasHigher) {
 47                 System.out.println("get end:" + bdHigher);
 48                 TreeSet<BigDecimal> newTargetSet = (TreeSet<BigDecimal>) targetSet
 49                         .headSet(bdHigher, true);
 50                 BigDecimal newStart = null;
 51                 SortedSet<BigDecimal> returnSet = null;
 52 
 53                 if (bdLower.compareTo(targetSet.first()) == -1) {
 54                     newStart = targetSet.first();
 55                 } else {
 56                     for (BigDecimal object : newTargetSet) {
 57                         if (object.compareTo(bdLower) != -1) {
 58                             newStart = object;
 59                             break;
 60                         }
 61                     }
 62                 }
 63                 returnSet = targetSet.subSet(newStart, true, bdHigher, true);
 64 
 65                 return returnSet;
 66             } else {
 67                 System.out.println("Not get start:" + bdLower);
 68                 System.out.println("Not get end:" + bdHigher);
 69                 BigDecimal newStart = null;
 70                 BigDecimal newEnd = null;
 71                 if (bdHigher.compareTo(targetSet.last()) != -1) {
 72                     newEnd = targetSet.last();
 73                 }
 74                 if (bdLower.compareTo(targetSet.first()) == -1) {
 75                     newStart = targetSet.first();
 76                 }
 77                 for (BigDecimal object : targetSet) {
 78                     if (newStart == null) {
 79                         if (object.compareTo(bdLower) != -1) {
 80                             newStart = object;
 81                             if (newEnd != null) {
 82                                 break;
 83                             }
 84                         }
 85                     }
 86 
 87                     if (newEnd == null) {
 88                         if (object.compareTo(bdHigher) != -1) {
 89                             newEnd = object;
 90                             if (newStart != null) {
 91                                 break;
 92                             }
 93                         }
 94                     }
 95                 }
 96 
 97                 if (newStart == null) {
 98                     if (newEnd == null) {
 99                         if ((bdHigher.compareTo(targetSet.first()) == -1)
100                                 || (bdLower.compareTo(targetSet.last()) == 1)) {
101                             return null;
102                         }
103                         return targetSet;
104                     } else {
105                         SortedSet<BigDecimal> newTargetSet = targetSet.headSet(
106                                 newEnd, true);
107                         if (newEnd.compareTo(bdHigher) == 1) {
108                             newTargetSet.remove(newEnd);
109                         }
110                         return newTargetSet;
111                     }
112                 } else {
113                     if (newEnd == null) {
114                         SortedSet<BigDecimal> newTargetSet = targetSet.tailSet(
115                                 newStart, true);
116                         return newTargetSet;
117                     } else {
118                         SortedSet<BigDecimal> newTargetSet = targetSet.subSet(
119                                 newStart, true, newEnd, true);
120                         if (newEnd.compareTo(bdHigher) == 1) {
121                             newTargetSet.remove(newEnd);
122                         }
123                         return newTargetSet;
124                     }
125                 }
126             }
127         }
128     }
第三种方式: 使用Apache Commons Collections, 直接对于原始Set进行filter.
            测试效果:集合大小10M,过滤结果1M, 运算时间: 1000ms+
过滤部分的代码如下:
 1 //过滤的主体逻辑
 2     void filterSet(Set<BigDecimal> targetSet, String lower, String higher) {
 3         final BigDecimal bdLower = new BigDecimal(Double.parseDouble(lower));
 4         final BigDecimal bdHigher = new BigDecimal(Double.parseDouble(higher));
 5 
 6         Predicate predicate = new Predicate() {
 7             public boolean evaluate(Object object) {
 8                 BigDecimal bDObject = (BigDecimal) object;
 9                 return bDObject.compareTo(bdLower) >= 0
10                         && bDObject.compareTo(bdHigher) <= 0;
11             }
12         };
13 
14         CollectionUtils.filter(targetSet, predicate);
15     }

第四种方式:使用Guava(google Collections), 直接对于原始Set进行Filter
            测试效果:集合大小10M,过滤结果1M, 运算时间: 100ms-
过滤部分的代码如下:
 1 //guava filter
 2 
 3     Set<BigDecimal> filterSet(Set<BigDecimal> targetSet, String lower,
 4             String higher) {
 5         final BigDecimal bdLower = new BigDecimal(Double.parseDouble(lower));
 6         final BigDecimal bdHigher = new BigDecimal(Double.parseDouble(higher));
 7 
 8         Set<BigDecimal> filterCollection = Sets.filter(targetSet,
 9                 new Predicate<BigDecimal>() {
10                     @Override
11                     public boolean apply(BigDecimal input) {
12                         BigDecimal bDObject = (BigDecimal) input;
13                         return bDObject.compareTo(bdLower) >= 0
14                                 && bDObject.compareTo(bdHigher) <= 0;
15                     }
16                 });
17 
18         return filterCollection;
19     }


四种方式对比如下:
第一种方式:  仅依赖于JAVA原生类库 遍历时间最慢, 代码量很小
第二种方式:  仅依赖于JAVA原生类库 遍历时间比较慢(主要慢在生成有序Set), 代码量最多
第三种方式:  依赖于Apache Commons Collections, 遍历时间比较快, 代码量很少
第四种方式:  依赖于Guava, 遍历时间最快, 代码量很少

基于目前个人的技术水平和视野, 第四种方式可能是最佳选择.

记录一下, 以后可能还会有更好的方案.




posted @ 2014-06-21 23:33 混沌中立 阅读(7347) | 评论 (10)编辑 收藏
  2009年9月2日
在几年之前,在大学里面的时候,认为系统的架构设计,就像建筑设计一样,会把骨架搭成,然后有具体人员进行详细的开发.

在后来,工作中,慢慢有了一些变化,因为原先的想法不太切合实际,系统就是在变化之中的,如果固定了骨架,那就很难的敏捷面对变化.
所以,系统的架构设计,应该是面向接口的设计,确定各个部分之间的数据接口和方法接口.这样,即使有了变化,只要遵循接口的定义,就还是可以面对变化.


最近,又有了想法的变化.架构的设计,应该就是规则和规约的设计.设计出一系列,统一的,和谐的规则,在这些规则之前圈住的部分,实际就是系统的全貌.
接口的设计,实际只是规则和规约设计的一个部分.
架构的设计,不应该只是程序方面的事情,同时也包含了心理学方面和社会学方面的一些规则.例如:团队在面对变化时候,需要采用的规则和流程.
只有包含了这些非程序上的规则之后,才能保证架构风格的统一协调.


以上,是我对系统设计的想法的转变过程.记录于此,以供回溯.




posted @ 2009-09-02 10:53 混沌中立 阅读(237) | 评论 (0)编辑 收藏
  2009年8月20日
面对着满屏幕的程序
是三年前,项目刚刚启动的时候,同事写的代码.
三年过去了,项目由第一期变成了第七期.

这段代码还是在这里,有个属性是list,其中每个cell都是一个长度18的String数组.
数组里面放置了所需要导出到页面table的内容.

现在要开始修改了,需要向页面的table中增加4列.
繁琐的让人要命的工作,需要跟踪这个循环,判断每个pattern下面,这个长度18的数组里面放了哪些内容.

好吧,对象化维护从数组开始,把数组对折,因为这个数组时一个比较数组,前面9个元素是之前的情况,后面9个事之后的情况.
用一个bean,放入两次就可以了.但是bean中,需要一个标志,标识是之前的情况还是之后的情况.

同时需要一个transform方法,把之前从几个来源过来的情况,变成bean的属性.
接下来需要一个values方法,把bean里面的属性直接按顺序转化成数组.
本期新增的4个属性,直接放入bean中就可以了.

这样原来很复杂的数组,就可以简单的用对象来解决.外部的接口完全没有变化.

维护程序,从把数组(特别是异型数组)对象化开始.

posted @ 2009-08-20 13:43 混沌中立 阅读(1342) | 评论 (1)编辑 收藏
  2006年7月13日
这个小的project是前一个阶段,待业在家的时候,迷恋sudoku的时候,自己写来玩的。
正好当时在看Uncle Bob的《Agile Software Development: Principles, Patterns, and Practices》 (敏捷软件开发:原则、模式与实践),所以就按照自己对书中的一些概念和方法的理解,结合自己之前的开发经验写出来一段小的代码。

代码行数: < 900
类的个数: 18
抽象类的个数:2
工厂类的个数:1
包的个数:5

一些关于类和包作用的说明:
1.Cell:表示一个Cell,是一个游戏中的一个单元格。
  Cell主要由3个部分组成,Point,Value,Status.
2.Point:表示一个坐标,主要格式为:(2,3).
  !!!注意:由于个人比较懒,所以开始的错误被贯彻了下来。
  这个错误就是(2,3)表示的是由最左上的位置为坐标原点,第二行和第三列所确定的那个单元格。也就是纵坐标在前,横坐标在后了。
3.Value:表示一个值
4.Status:表示Cell的状态,只有两个状态,一个是NotSure,另一个是Sure.

5.AbstractCells:表示一些cell的集合,主要有三个子类
     BlockCells:表示一个由多个Cell组成的块,例如一个2*2由4个Cell组成的块,或者一个2*3由6个Cell组成的块
     HorizonCells:表示一个横行,即:从(0,0)到(0,n)坐标确定的所有Cell的集合。
     VerticalCells:表示一个纵行,即:从(0,0)到(n,0)坐标确定的所有Cell的集合。
6.AbstractPolicy:就是游戏的策略。
   这个主要表示的是:4*4的游戏,还是9*9的游戏。
   可以在以后对此类进行继承和扩展,例如16*16的游戏我就没有实现。
   主要扩展3个方法:
                  1)getValueRange,返回当前policy的value的个数。4*4的游戏的getValueRange返回的就应该是4。
          2)getStep:表示当前policy中相邻的两个BlockCells的坐标差。
          3)getIncrease:说不明白了:)(只可意会不可言传。)
7.Game:进行Policy的场所(我一直想抛弃这个类)
8.TestGame:游戏运行的地方,包括从PolicyFactory取得指定的Policy,设置输入输出文件的路径。
9.PolicyFactory:取得Policy的工厂。
    getPolicy(int x) :这个方法获得的是正方形的sudoku的策略。例如:4*4的,9*9,16*16。
    getPolicy(int x, int y):这个方法获得的是长方形的Sudoku的策略。例如:9*12的。


虽然是尽量避免bad code smell,但是由于能力有限,还是出现了一些不好的地方。
例如:之间的关联关系还是很多,而且很强;抽象的方法和抽象类的个数偏少等等。

里面实现了三个解决sudoku的方法:
1.在一个Cell中出现的Value,不会在和这个Cell处在同一个AbstractCells中的所有Cell中出现;
2.如果一个Cell中,所有可能出现的Value的个数为1,那么Cell的Value必然是这个最后的Value;
2.如果一个Value,如果在当前AbstractCells的所有其他的Cell中都不可能出现,那么它必然是最后一个Cell的Value。

附件1:src code
http://www.blogjava.net/Files/GandofYan/sudoku.rar
附件2:输入输出文件的example
http://www.blogjava.net/Files/GandofYan/temp.rar

posted @ 2006-07-13 16:19 混沌中立 阅读(2150) | 评论 (4)编辑 收藏
  2006年6月7日
如同Tom DeMacro说的:无法控制的东西就不能管理,无法测量的东西就无法控制。
软件的度量对于设计者和开发者非常重要,之前只是对这些有一个简单的了解。今天看来,了解的还远远不够。
  • Cyclomatic Complexity (圈复杂性)
  • Response for Class (类的响应)
  • Weighted methods per class (每个类重量方法)
一个系统中的所有类的这三个度量能够说明这个系统的设计上的一些问题(不是全部),这三个度量越大越不好。
如果一个类这三个度量很高,证明了这个类需要重构了。

以第一个度量来说,有下面的一个表格:

CC Value

Risk

1-10

Low risk program

11-20

Moderate risk

21-50

High risk

>50

Most complex and highly unstable method


CC数值高,可以通过减少if else(switch case也算)判断来达到目的;
可以通过减少类与其他类的调用来减少RFC;
通过分割大方法和大类来达到减少WMPC.

而Uncle Bob和Jdepend的度量标准应该算是另一个度量系统。

  • 关系内聚性(H)
用包中的每个类平均的内部关系数目作为包内聚性的一种表示方式。用于表示包和它的所有类之间的关系。
H=(R+1)/N
R:包内类的关系数目(与包外部的类没有关系)
N:包内类的数量

  • Number of Classes (Cc)
被分析package的具体和抽象类(和接口)的数量,用于衡量package的可扩展性。

  • Afferent Couplings (Ca)
依赖于被分析package的其他package的数量,用于衡量pacakge的职责。
  • Efferent Couplings (Ce)
被分析package的类所依赖的其他package的数量,用于衡量package的独立性。
  • Abstractness (A)
被分析package中的抽象类和接口与所在package所有类数量的比例,取值范围为0-1。
A=Cc/N
  • Instability (I)
用于衡量package的不稳定性,取值范围为0-1。I=0表示最稳定,I=1表示最不稳定。
I=Ce/(Ce+Ca)
  • Distance (D)
          被分析package和理想曲线A+I=1的垂直距离,用于衡量package在稳定性和抽象性之间的平衡。理想          的package要么完全是抽象类和稳定(x=0,y=1),要么完全是具体类和不稳定(x=1,y=0)。
          取值范围为0-1,D=0表示完全符合理想标准,D=1表示package最大程度地偏离了理想标准。
          D = |A+I-1|/0.70710678
          注:0.70710678*0.70710678 =2,既为“根号2“

我认为D是一个综合的度量,架构和设计的改善可以通过D数值的减少来体现,反之就可以认为是设计和架构的退化。


读过http://javaboutique.internet.com/tutorials/metrics/index.html之后的一些想法

另一篇中文的内容相近的文章可以参考http://www.jdon.com/artichect/coupling.htm

不过第二篇的中文文章中间关于Cyclomatic Complexity,有一个情况遗漏了
public void findApplications(String id, String name){

if(id!=null && name!=null) {
//do something
}else{
//do something
}
}
这种情况的CC不是2+1,而是2+1+1,依据是公式(1)。公式(2)应该是公式(1)的简化版。
Cyclomatic ComplexityCC) = no of decision points + no of logical operations +1        (1)

Cyclomatic Complexity (CC) = number of decision points +1 (2)

参考了JDepend的参数和Uncle Bob的《
Agile Software Development: Principles, Patterns, and Practices(敏捷软件开发:原则、模式与实践)
posted @ 2006-06-07 10:52 混沌中立 阅读(1500) | 评论 (3)编辑 收藏
  2006年5月30日
转自:http://www.keyusoft.cn/Contentview.aspx?year=2005&month=$10&day=$6&postid=123

通过一周左右的研究,对规则引擎有了一定的了解。现在写点东西跟大家一起交流,本文主要针对RETE算法进行描述。我的文笔不太好,如果有什么没讲明白的或是说错的地方,请给我留言。
首先申明,我的帖子借鉴了网上很流行的一篇帖子,好像是来自CSDN;还有一点,我不想做太多的名词解释,因为我也不是个研究很深的人,定义的不好怕被笑话。
好现在我们开始。
首先介绍一些网上对于规则引擎比较好的帖子。
1、 来自JAVA视频网
http://forum.javaeye.com/viewtopic.php?t=7803&postdays=0&postorder=asc&start=0
2、  RETE算法的最原始的描述,我不知道在哪里找到的,想要的人可以留下E-mail
3、  CMU的一位博士生的毕业论文,个人觉得非常好,我的很多观点都是来自这里的,要的人也可以给我发mail   mailto:ipointer@163.com
 
接着统一一下术语,很多资料里的术语都非常混乱。
1、  facts 事实,我们实现的时候,会有一个事实库。用F表示。
2、  patterns 模板,事实的一个模型,所有事实库中的事实都必须满足模板中的一个。用P表示。
3、   conditions 条件,规则的组成部分。也必须满足模板库中的一条模板。用C表示。我们可以这样理解facts、patterns、conditions之间的关系。 Patterns是一个接口,conditions则是实现这个接口的类,而facts是这个类的实例。
4、  rules 规则,由一到多个条件构成。一般用and或or连接conditions。用R表示。
5、  actions 动作,激活一条rule执行的动作。我们这里不作讨论。
6、  还有一些术语,如:working-memory、production-memory,跟这里的概念大同小异。
7、  还有一些,如:alpha-network、beta-network、join-node,我们下面会用到,先放一下,一会讨论。
 
引用一下网上很流行的例子,我觉得没讲明白,我在用我的想法解释一下。
 
假设在规则记忆中有下列三条规则
 
if A(x) and B(x) and C(y) then add D(x)
if A(x) and B(y) and D(x) then add E(x)
if A(x) and B(x) and E(x) then delete A(x)
 
RETE算法会先将规则编译成下列的树状架构排序网络

而工作记忆内容及顺序为{A(1),A(2),B(2),B(3),B(4),C(5)},当工作记忆依序进入网络后,会依序储存在符合条件的节点中,直到完全符合条件的推论规则推出推论。以上述例子而言, 最后推得D(2)。
 
让我们来分析这个例子。
 
模板库:(这个例子中只有一个模板,算法原描述中有不同的例子, 一般我们会用tuple,元组的形式来定义facts,patterns,condition)
P: (?A , ?x)  其中的A可能代表一定的操作,如例子中的A,B,C,D,E ; x代表操作的参数。看看这个模板是不是已经可以描述所有的事实。
 
条件库:(这里元组的第一项代表实际的操作,第二项代表形参)
C1: (A , <x>)
C2: (B , <x>)
C3: (C , <y>)
C4: (D , <x>)
C5: (E , <x>)
C6: (B , <y>)
 
事实库:(第二项代表实参)
F1: (A,1)
F2: (A,2)
F3: (B,2)
F4: (B,3)
F5: (B,4)
F6: (C,5)
 
       规则库:
         R1: c1^c2^c3
         R2: c1^c2^c4
         R3: c1^c2^c5
 
      
       有人可能会质疑R1: c1^c2^c3,没有描述出,原式中:
if A(x) and B(x) and C(y) then add D(x),A=B的关系。但请仔细看一下,这一点已经在条件库中定义出来了。
 
       下面我来描述一下,规则引擎中RETE算法的实现。
       首先,我们要定一些规则,根据这些规则,我们的引擎可以编译出一个树状结构,上面的那张图中是一种简易的表现,其实在实现的时候不是这个样子的。
       这就是beta-network出场的时候了,根据rules我们就可以确定beta-network,下面,我就画出本例中的beta-network,为了描述方便,我把alpha-network也画出来了。
      
 
上图中,左边的部分就是beta-network,右边是alpha-network,圆圈是join-node.
从上图中,我们可以验证,在beta-network中,表现出了rules的内容,其中r1,r2,r3共享了许多BM和join-node,这是由于这些规则中有共同的部分,这样能加快match的速度。
右 边的alpha-network是根据事实库构建的,其中除alpha-network节点的节点都是根据每一条condition,从事实库中 match过来的,这一过程是静态的,即在编译构建网络的过程中已经建立的。只要事实库是稳定的,即没有大幅度的变化,RETE算法的执行效率应该是非常 高的,其原因就是已经通过静态的编译,构建了alpha-network。我们可以验证一下,满足c1的事实确实是w1,w2。
下 面我们就看一下,这个算法是怎么来运行的,即怎么来确定被激活的rules的。从top-node往下遍历,到一个join-node,与AM for c1的节点汇合,运行到match c1节点。此时,match c1节点的内容就是:w1,w2。继续往下,与AM for c2汇合(所有可能的组合应该是w1^w3,w1^w4,w1^w5,w2^w3,w2^w4,w2^w5),因为c1^c2要求参数相同,因此, match c1^c2的内容是:w2^w3。再继续,这里有一个扇出(fan-out),其中只有一个join-node可以被激活,因为旁边的AM只有一个非空。 因此,也只有R1被激活了。
解决扇出带来的效率降低的问题,我们可以使用hashtable来解决这个问题。
RETE算法还有一些问题,如:facts库变化,我们怎么才能高效的重建alpha-network,同理包括rules的变化对beta-network的影响。这一部分我还没细看,到时候再贴出来吧。
posted @ 2006-05-30 15:30 混沌中立 阅读(1034) | 评论 (2)编辑 收藏
最近插件又加多了,eclipse老是死掉
 
一怒之下,删除重装
 
以前因为懒,没有把插件的目录和主体的目录分开,这次也给它分开了
 
 
插件少了之后,eclipse确实快了不少
 
附eclipse启动参数: -nl en_US vmargs -Xverify:none -Xms256M -Xmx1024M -XX:PermSize=50M  -XX:+UseParallelGC
posted @ 2006-05-30 13:48 混沌中立 阅读(559) | 评论 (0)编辑 收藏
freemind一个比较不错free的 mind map 软件,很多人建议使用这个来管理自己的思路.
 
mind manager另一个比较不错的mind map的软件,可以和office兼容.不过是商业的
 
visio,就不介绍了.office里面有的东西.做流程图来说,确实是比较好的软件.但是在思路不清楚的时候,很难画出什么有用的东西来.这点就比不上前面两个东西了.不过对我来说visio可能更顺手,因为我经常画的是软件流程图........
posted @ 2006-05-30 13:36 混沌中立 阅读(1742) | 评论 (2)编辑 收藏
总结一下最近关于domain object以及相关的讨论
 
在最近的围绕domain object的讨论中浮现出来了三种模型,(还有一些其他的旁枝,不一一分析了),经过一番讨论,各种问题逐渐清晰起来,在这里我试图做一个总结,便于大家了解和掌握。

第一种模型:只有getter/setter方法的纯数据类,所有的业务逻辑完全由business object来完成(又称TransactionScript),这种模型下的domain object被Martin Fowler称之为“贫血的domain object”。下面用举一个具体的代码来说明,代码来自Hibernate的caveatemptor,但经过我的改写:

一个实体类叫做Item,指的是一个拍卖项目
一个DAO接口类叫做ItemDao
一个DAO接口实现类叫做ItemDaoHibernateImpl
一个业务逻辑类叫做ItemManager(或者叫做ItemService)

java代码: 

public class Item implementsSerializable{
    privateLong id = null;
    privateint version;
    privateString name;
    private User seller;
    privateString description;
    private MonetaryAmount initialPrice;
    private MonetaryAmount reservePrice;
    privateDate startDate;
    privateDate endDate;
    privateSet categorizedItems = newHashSet();
    privateCollection bids = newArrayList();
    private Bid successfulBid;
    private ItemState state;
    private User approvedBy;
    privateDate approvalDatetime;
    privateDate created = newDate();
    //  getter/setter方法省略不写,避免篇幅太长
}



java代码: 

public interface ItemDao {
    public Item getItemById(Long id);
    publicCollection findAll();
    publicvoid updateItem(Item item);
}



ItemDao定义持久化操作的接口,用于隔离持久化代码。

java代码: 

public class ItemDaoHibernateImpl implements ItemDao extends HibernateDaoSupport {
    public Item getItemById(Long id){
        return(Item) getHibernateTemplate().load(Item.class, id);
    }
    publicCollection findAll(){
        return(List) getHibernateTemplate().find("from Item");
    }
    publicvoid updateItem(Item item){
        getHibernateTemplate().update(item);
    }
}


ItemDaoHibernateImpl完成具体的持久化工作,请注意,数据库资源的获取和释放是在ItemDaoHibernateImpl 里面处理的,每个DAO方法调用之前打开Session,DAO方法调用之后,关闭Session。(Session放在ThreadLocal中,保证一次调用只打开关闭一次)

java代码: 

public class ItemManager {
    private ItemDao itemDao;
    publicvoid setItemDao(ItemDao itemDao){ this.itemDao = itemDao;}
    public Bid loadItemById(Long id){
        itemDao.loadItemById(id);
    }
    publicCollection listAllItems(){
        return  itemDao.findAll();
    }
    public Bid placeBid(Item item, User bidder, MonetaryAmount bidAmount,
                            Bid currentMaxBid, Bid currentMinBid)throws BusinessException {
            if(currentMaxBid != null && currentMaxBid.getAmount().compareTo(bidAmount) > 0){
            throw new BusinessException("Bid too low.");
    }
   
    // Auction is active
    if( !state.equals(ItemState.ACTIVE))
            throw new BusinessException("Auction is not active yet.");
   
    // Auction still valid
    if( item.getEndDate().before(newDate()))
            throw new BusinessException("Can't place new bid, auction already ended.");
   
    // Create new Bid
    Bid newBid = new Bid(bidAmount, item, bidder);
   
    // Place bid for this Item
    item.getBids().add(newBid);
    itemDao.update(item);     //  调用DAO完成持久化操作
    return newBid;
    }
}



事务的管理是在ItemManger这一层完成的,ItemManager实现具体的业务逻辑。除了常见的和CRUD有关的简单逻辑之外,这里还有一个placeBid的逻辑,即项目的竞标。

以上是一个完整的第一种模型的示例代码。在这个示例中,placeBid,loadItemById,findAll等等业务逻辑统统放在ItemManager中实现,而Item只有getter/setter方法。

 

 

第二种模型,也就是Martin Fowler指的rich domain object是下面这样子的:

一个带有业务逻辑的实体类,即domain object是Item
一个DAO接口ItemDao
一个DAO实现ItemDaoHibernateImpl
一个业务逻辑对象ItemManager

java代码: 

public class Item implementsSerializable{
    //  所有的属性和getter/setter方法同上,省略
    public Bid placeBid(User bidder, MonetaryAmount bidAmount,
                        Bid currentMaxBid, Bid currentMinBid)
            throws BusinessException {
   
            // Check highest bid (can also be a different Strategy (pattern))
            if(currentMaxBid != null && currentMaxBid.getAmount().compareTo(bidAmount) > 0){
                    throw new BusinessException("Bid too low.");
            }
   
            // Auction is active
            if( !state.equals(ItemState.ACTIVE))
                    throw new BusinessException("Auction is not active yet.");
   
            // Auction still valid
            if( this.getEndDate().before(newDate()))
                    throw new BusinessException("Can't place new bid, auction already ended.");
   
            // Create new Bid
            Bid newBid = new Bid(bidAmount, this, bidder);
   
            // Place bid for this Item
            this.getBids.add(newBid)// 请注意这一句,透明的进行了持久化,但是不能在这里调用ItemDao,Item不能对ItemDao产生依赖!
   
            return newBid;
    }
}



竞标这个业务逻辑被放入到Item中来。请注意this.getBids.add(newBid); 如果没有Hibernate或者JDO这种O/R Mapping的支持,我们是无法实现这种透明的持久化行为的。但是请注意,Item里面不能去调用ItemDAO,对ItemDAO产生依赖!

ItemDao和ItemDaoHibernateImpl的代码同上,省略。

java代码: 

public class ItemManager {
    private ItemDao itemDao;
    publicvoid setItemDao(ItemDao itemDao){ this.itemDao = itemDao;}
    public Bid loadItemById(Long id){
        itemDao.loadItemById(id);
    }
    publicCollection listAllItems(){
        return  itemDao.findAll();
    }
    public Bid placeBid(Item item, User bidder, MonetaryAmount bidAmount,
                            Bid currentMaxBid, Bid currentMinBid)throws BusinessException {
        item.placeBid(bidder, bidAmount, currentMaxBid, currentMinBid);
        itemDao.update(item);    // 必须显式的调用DAO,保持持久化
    }
}



在第二种模型中,placeBid业务逻辑是放在Item中实现的,而loadItemById和findAll业务逻辑是放在 ItemManager中实现的。不过值得注意的是,即使placeBid业务逻辑放在Item中,你仍然需要在ItemManager中简单的封装一层,以保证对placeBid业务逻辑进行事务的管理和持久化的触发。

这种模型是Martin Fowler所指的真正的domain model。在这种模型中,有三个业务逻辑方法:placeBid,loadItemById和findAll,现在的问题是哪个逻辑应该放在Item 中,哪个逻辑应该放在ItemManager中。在我们这个例子中,placeBid放在Item中(但是ItemManager也需要对它进行简单的封装),loadItemById和findAll是放在ItemManager中的。

切分的原则是什么呢? Rod Johnson提出原则是“case by case”,可重用度高的,和domain object状态密切关联的放在Item中,可重用度低的,和domain object状态没有密切关联的放在ItemManager中。

我提出的原则是:看业务方法是否显式的依赖持久化。

Item的placeBid这个业务逻辑方法没有显式的对持久化ItemDao接口产生依赖,所以要放在Item中。请注意,如果脱离了Hibernate这个持久化框架,Item这个domain object是可以进行单元测试的,他不依赖于Hibernate的持久化机制。它是一个独立的,可移植的,完整的,自包含的域对象

而loadItemById和findAll这两个业务逻辑方法是必须显式的对持久化ItemDao接口产生依赖,否则这个业务逻辑就无法完成。如果你要把这两个方法放在Item中,那么Item就无法脱离Hibernate框架,无法在Hibernate框架之外独立存在。

 

 

第三种模型印象中好像是firebody或者是Archie提出的(也有可能不是,记不清楚了),简单的来说,这种模型就是把第二种模型的domain object和business object合二为一了。所以ItemManager就不需要了,在这种模型下面,只有三个类,他们分别是:

Item:包含了实体类信息,也包含了所有的业务逻辑
ItemDao:持久化DAO接口类
ItemDaoHibernateImpl:DAO接口的实现类

由于ItemDao和ItemDaoHibernateImpl和上面完全相同,就省略了。

java代码: 

public class Item implementsSerializable{
    //  所有的属性和getter/setter方法都省略
   privatestatic ItemDao itemDao;
    publicvoid setItemDao(ItemDao itemDao){this.itemDao = itemDao;}
   
    publicstatic Item loadItemById(Long id){
        return(Item) itemDao.loadItemById(id);
    }
    publicstaticCollection findAll(){
        return(List) itemDao.findAll();
    }

    public Bid placeBid(User bidder, MonetaryAmount bidAmount,
                    Bid currentMaxBid, Bid currentMinBid)
    throws BusinessException {
   
        // Check highest bid (can also be a different Strategy (pattern))
        if(currentMaxBid != null && currentMaxBid.getAmount().compareTo(bidAmount) > 0){
                throw new BusinessException("Bid too low.");
        }
       
        // Auction is active
        if( !state.equals(ItemState.ACTIVE))
                throw new BusinessException("Auction is not active yet.");
       
        // Auction still valid
        if( this.getEndDate().before(newDate()))
                throw new BusinessException("Can't place new bid, auction already ended.");
       
        // Create new Bid
        Bid newBid = new Bid(bidAmount, this, bidder);
       
        // Place bid for this Item
        this.addBid(newBid);
        itemDao.update(this);      //  调用DAO进行显式持久化
        return newBid;
    }
}



在这种模型中,所有的业务逻辑全部都在Item中,事务管理也在Item中实现。

 

 

在上面三种模型之外,还有很多这三种模型的变种,例如partech的模型就是把第二种模型中的DAO和 Manager三个类合并为一个类后形成的模型;例如frain....(id很长记不住)的模型就是把第三种模型的三个类完全合并为一个单类后形成的模型;例如Archie是把第三种模型的Item又分出来一些纯数据类(可能是,不确定)形成的一个模型。

但是不管怎么变,基本模型归纳起来就是上面的三种模型,下面分别简单评价一下:

第一种模型绝大多数人都反对,因此反对理由我也不多讲了。但遗憾的是,我观察到的实际情形是,很多使用Hibernate的公司最后都是这种模型,这里面有很大的原因是很多公司的技术水平没有达到这种层次,所以导致了这种贫血模型的出现。从这一点来说,Martin Fowler的批评声音不是太响了,而是太弱了,还需要再继续呐喊。

第二种模型就是Martin Fowler一直主张的模型,实际上也是我一直在实际项目中采用这种模型。我没有看过Martin的POEAA,之所以能够自己摸索到这种模型,也是因为从02年我已经开始思考这个问题并且寻求解决方案了,但是当时没有看到Hibernate,那时候做的一个小型项目我已经按照这种模型来做了,但是由于没有O/R Mapping的支持,写到后来又不得不全部改成贫血的domain object,项目做完以后再继续找,随后就发现了Hibernate。当然,现在很多人一开始就是用Hibernate做项目,没有经历过我经历的那个阶段。

不过我觉得这种模型仍然不够完美,因为你还是需要一个业务逻辑层来封装所有的domain logic,这显得非常罗嗦,并且业务逻辑对象的接口也不够稳定。如果不考虑业务逻辑对象的重用性的话(业务逻辑对象的可重用性也不可能好),很多人干脆就去掉了xxxManager这一层,在Web层的Action代码直接调用xxxDao,同时容器事务管理配置到Action这一层上来。 Hibernate的caveatemptor就是这样架构的一个典型应用。

第三种模型是我很反对的一种模型,这种模型下面,Domain Object和DAO形成了双向依赖关系,无法脱离框架测试,并且业务逻辑层的服务也和持久层对象的状态耦合到了一起,会造成程序的高度的复杂性,很差的灵活性和糟糕的可维护性。也许将来技术进步导致的O/R Mapping管理下的domain object发展到足够的动态持久透明化的话,这种模型才会成为一个理想的选择。就像O/R Mapping的流行使得第二种模型成为了可能(O/R Mapping流行以前,我们只能用第一种模型,第二种模型那时候是不现实的)。

 

 

既然大家都统一了观点,那么就有了一个很好的讨论问题的基础了。Martin Fowler的Domain Model,或者说我们的第二种模型难道是完美无缺的吗?当然不是,接下来我就要分析一下它的不足,以及可能的解决办法,而这些都来源于我个人的实践探索。

在第二种模型中,我们可以清楚的把这4个类分为三层:

1、实体类层,即Item,带有domain logic的domain object
2、DAO层,即ItemDao和ItemDaoHibernateImpl,抽象持久化操作的接口和实现类
3、业务逻辑层,即ItemManager,接受容器事务控制,向Web层提供统一的服务调用

在这三层中我们大家可以看到,domain object和DAO都是非常稳定的层,其实原因也很简单,因为domain object是映射数据库字段的,数据库字段不会频繁变动,所以domain object也相对稳定,而面向数据库持久化编程的DAO层也不过就是CRUD而已,不会有更多的花样,所以也很稳定。

问题就在于这个充当business workflow facade的业务逻辑对象,它的变动是相当频繁的。业务逻辑对象通常都是无状态的、受事务控制的、Singleton类,我们可以考察一下业务逻辑对象都有哪几类业务逻辑方法:

第一类:DAO接口方法的代理,就是上面例子中的loadItemById方法和findAll方法。

ItemManager之所以要代理这种类,目的有两个:向Web层提供统一的服务调用入口点和给持久化方法增加事务控制功能。这两点都很容易理解,你不能既给Web层程序员提供xxxManager,也给他提供xxxDao,所以你需要用xxxManager封装xxxDao,在这里,充当了一个简单代理功能;而事务控制也是持久化方法必须的,事务可能需要跨越多个DAO方法调用,所以必须放在业务逻辑层,而不能放在DAO层。

但是必须看到,对于一个典型的web应用来说,绝大多数的业务逻辑都是简单的CRUD逻辑,所以这种情况下,针对每个DAO方法,xxxManager都需要提供一个对应的封装方法,这不但是非常枯燥的,也是令人感觉非常不好的。


第二类:domain logic的方法代理。就是上面例子中placeBid方法。虽然Item已经有了placeBid方法,但是ItemManager仍然需要封装一下Item的placeBid,然后再提供一个简单封装之后的代理方法。

这和第一种情况类似,其原因也一样,也是为了给Web层提供一个统一的服务调用入口点和给隐式的持久化动作提供事务控制。

同样,和第一种情况一样,针对每个domain logic方法,xxxManager都需要提供一个对应的封装方法,同样是枯燥的,令人不爽的。


第三类:需要多个domain object和DAO参与协作的business workflow。这种情况是业务逻辑对象真正应该完成的职责。

在这个简单的例子中,没有涉及到这种情况,不过大家都可以想像的出来这种应用场景,因此不必举例说明了。

通过上面的分析可以看出,只有第三类业务逻辑方法才是业务逻辑对象真正应该承担的职责,而前两类业务逻辑方法都是“无奈之举”,不得不为之的事情,不但枯燥,而且令人沮丧。




分析完了业务逻辑对象,我们再回头看一下domain object,我们要仔细考察一下domain logic的话,会发现domain logic也分为两类:

第一类:需要持久层框架隐式的实现透明持久化的domain logic,例如Item的placeBid方法中的这一句:

java代码: 

this.getBids().add(newBid);


上面已经着重提到,虽然这仅仅只是一个Java集合的添加新元素的操作,但是实际上通过事务的控制,会潜在的触发两条SQL:一条是insert一条记录到bid表,一条是更新item表相应的记录。如果我们让Item脱离Hibernate进行单元测试,它就是一个单纯的Java集合操作,如果我们把他加入到Hibernate框架中,他就会潜在的触发两条SQL,这就是隐式的依赖于持久化的domain logic
特别请注意的一点是:在没有Hibernate/JDO这类可以实现“透明的持久化”工具出现之前,这类domain logic是无法实现的。

对于这一类domain logic,业务逻辑对象必须提供相应的封装方法,以实现事务控制。


第二类:完全不依赖持久化的domain logic,例如readonly例子中的Topic,如下:

java代码: 

class Topic {
    boolean isAllowReply(){
        Calendar dueDate = Calendar.getInstance();
        dueDate.setTime(lastUpdatedTime);
        dueDate.add(Calendar.DATE, forum.timeToLive);
   
        Date now = newDate();
        return now.after(dueDate.getTime());
    }
}



注意这个isAllowReply方法,他和持久化完全不发生一丁点关系。在实际的开发中,我们同样会遇到很多这种不需要持久化的业务逻辑(主要发生在日期运算、数值运算和枚举运算方面),这种domain logic不管脱离不脱离所在的框架,它的行为都是一致的。对于这种domain logic,业务逻辑层并不需要提供封装方法,它可以适用于任何场合。
posted @ 2006-05-30 13:31 混沌中立 阅读(3036) | 评论 (1)编辑 收藏

原文地址:http://www.cnblogs.com/idior/archive/2005/07/04/186086.html

近日 有关o/r m的讨论突然多了起来. 在这里觉得有必要澄清一些概念, 免的大家讨论来讨论去, 才发现最根本的理解有问题.

1. 何谓实体?
实体(类似于j2ee中的Entity Bean)通常指一个承载数据的对象, 但是注意它也是可以有行为的! 只不过它的行为一般只操作自身的数据. 比如下面这个例子:


class Person
{
  string firstName;
  string lastName;

  public void GetName()
  {
     return  lastName+firstName;
  }   
}


GetName就是它的一个行为.

2 何谓对象?
对象最重要的特性在于它拥有行为. 仅仅拥有数据,你可以称它为对象, 但是它却失去它最重要的灵魂. 


class Person
{
  string firstName;
  string lastName;
  Role role;
  int baseWage;
  public void GetSalary()
  {
     return baseWage*role.GetFactory();
  }   
}

这样需要和别的对象(不是Value Object)打交道的对象,我就不再称其为实体. 领域模型就是指由这些具有业务逻辑的对象构成的模型.

3. E/R M or O/R M?!!
仔细想想我们为什么需要o/r m,无非是想利用oo的多态来处理复杂的业务逻辑, 而不是靠一堆的if else. 
而现在在很多人的手上o/r m全变成了e/r m.他们不考虑对象的行为, 而全关注于如何保存数据.这样也难怪他们会产生将CRUD这些操作放入对象中的念头. 如果你不能深刻理解oo, 那么我不推荐你使用o/r m, Table Gateway, Row Gateway才是你想要的东西.


作为一个O/R M框架,很重要的一点就是实现映射的透明性(Transparent),比较显著的特点就是在代码中我们是看不到SQL语句的(框架自动生成了)。这里所指的O/R M就是类似于此的框架,

4. POEAA中的相关概念
  很多次发现有人错用其中的概念, 这里顺便总结一下:
  1. Table Gateway
    以表为单位的实体,基本没有行为,只有CRUD操作.
  2. Row Gateway
    以行为单位的实体,基本没有行为,只有CRUD操作.
  3. Active Record
    以行为单位的实体,拥有一些基本的操作自身数据的行为(如上例中的GetName),同时包含有CRUD操作.
其实Active Record最符合某些简单的需求, 接近于E/R m.
通常也有很多人把它当作O/R m.不过需要注意的是Active Record中是充满了SQL语句的(不像orm的SQL透明), 所以有人想起来利用O/R m来实现"Active Record", 虽然在他们眼里看起来很方便, 其实根本就是返祖.
用CodeGenerator来实现Active Record也许是一个比较好的方法.
  4. Data Mapper
这才是真正的O/R m,Hibernate等等工具的目标.

5.O/R M需要关注的地方 (希望大家帮忙完善一下)
 1. 关联, O/R M是如何管理类之间的关联.当然这不仅于o/r m有关与设计者的设计水平也有很大关系.
 2. O/R M对继承关系的处理.
 3. O/R M对事务的支持.
 4. O/R M对查询的支持.
 
以上观点仅属个人意见, 不过在大家讨论有关O/R m之前, 希望先就一些基本概念达成共识, 不然讨论下去会越离越远. 


(建议: 如果对oo以及dp没有一定程度的了解, 最好别使用o/r m, dataset 加上codesmith或许是更好的选择)
posted @ 2006-05-30 13:20 混沌中立 阅读(359) | 评论 (0)编辑 收藏
仅列出标题  下一页