我爱oo,我爱java

交流blog QQ:421057986 oofrank@donews

2006年1月20日 #

DAO-持久层-领域对象-贫血模型

原文


关于"贫血模型"的讨论几乎没有停止过,在openfans.org的开发过程中,我们也讨论了很久,我觉的有很多东西应该记下来:
明确一下意思先:
DAO:数据操作对象,会操作数据库
持久层:能提供对象持久化服务的一系列组件或服务
领域对象:描述领域模型的对象,是通过业务分析进行系统建模的产物
贫 血模型:就是domain object只有属性的getter/setter方法的纯数据类,所有的业务逻辑完全由一个所谓的Manager来完成(又称 TransactionScript),这种模型下的domain object被Martin Fowler称之为“贫血的domain object”
常见的类基本结构如下:
一个业务数据类叫做Item,
一个DAO接口类叫做ItemDao
一个DAO接口实现类叫做ItemDaoHibernateImpl
一个业务逻辑类叫做ItemManager(或者叫做ItemService).

观察上面的几个类很容易发现问题:
1:Item和ItemManager实际是操作与数据的关系,实际完成的就是经典OO中的一个对象的能力;
2:当有许多Item时 类组变得很庞大,产生很多 xxxDao xxxImpl xxxManager 其中包含大量重复代码;
按<<重构>>的观点,上述代码存在以下臭味:
1:重复的代码   xxxDao xxxImpl xxxManager(通常)
2:霰弹式修改,一个变化影响多个类,类之间不够高内聚 item变化-->Dao,Impl,Manager均要变动
3:依恋情结,两个类之间互相作用过多 item<->Manager
4:平行继承体系,当增加一个新类时总是要增加另一个类
5:夸夸其谈未来性,在没有任何暗示的情况下考虑扩展  Dao,实际HibernateImpl可能n年内是唯一的Dao实现
6:纯稚的数据类,只有数据的类  item

我觉的 贫血模型 是系统分析设计方向性错误的产物:
1:没有进行领域建模---以数据表结构为中心,而不是业务模型为中心的思考方式,使设计人员选择Item为考虑问题的出发点
2:将DAO与持久层混淆---我们需要的一种持久化服务,DAO紧紧是提供数据操作能力而已,Hibernate是一种高级的服务(他已经包含了DAO,而不是相反),已经完成了所有的持久层服务.
3:过于强调低偶合---将一些本来一些提供单一职责的内容分散在多个单元中使 客户端 依赖更多的接口,而忘记了高内聚原则.
4:Spring的能力限制---由于Spring现阶段不支持对于领域模型的服务注入,使设计人员将操作和数据分开,并将领域变为DataOnly的.
  (Spring2.0将在很大程度上解决这个问题)
 
我认为良好的解决方案:
  首先领域建模,建立领域模型-->合并前面所说的Item和ItemManager成为 domainItem;对于数据库服务,
  1:如果考虑领域层包含数据操作能力,则建立DAO并选择其它好的DAO方案比如IBATIS或Hibernate之类的组件;
  2:如果考虑将数据库(或其他存储界质)存储考虑在领域之外成为持久层,
      a:则或者对持久层框架同时建模,同时选择合适的组件为持久层服务提供存储服务(包括DAO--亦可选择IBATIS/Hibernate组件),
      b:或者直接使用Hibernate/JDO等框架实现持久化服务,领域层直接使用持久层服务,对领域对象进行持久化和反持久化(从持久层获取以持久化的对象).
 
其他:
  实际上,作为一种解决方案,所谓"贫血模型"的具体使用,并不会有太大的问题,尤其是使用一些代码生成工具或已经做好相应的基本框架时,很多软件的核心价 值都在于对客户提供的服务,而其内部则成为黑盒,我们只要合理的解决业务问题,就是"王道"了,对于代码的臭味,可以慢慢重构--这也需要成本呀. 
 
再其他:
有人说,我们的业务就是CRUD,领域模型只有数据类就足够了.我觉的这是搞错了方向------只有CRUD时,只有处理CRUD的那些类才有必要进行建模(他们才是领域模型),而所谓的User\Item等数据类则完全没有必要进行建模,更不要谈领域了.

贫血之外:
实际上,软件\OO方法的外延大的很,更多问题与数据库存储无关(但也有贫血问题),所以建模才是根本,OO方法的原则才是我们必须掌握的.

posted @ 2006-04-10 22:21 兼听则明 阅读(6532) | 评论 (4)编辑 收藏

SQLServer的一个bug

SQLServer一个bug终于被我碰上了

我有一个表使用字符类型存储数字值,想进行汇总计算:

sum(case when isnumeric(FieldName)=0 then 0 else cast  (FieldName as numeric) end)
简单试了一下没有问题,可是今天数据中有一个 ’2.1234567E7‘  isnumeric返回1 cast 返回错误

呜呜。。。。
怎么办......

posted @ 2006-02-05 22:37 兼听则明 阅读(337) | 评论 (0)编辑 收藏

使用Quartz要注意的一个问题


当设置一个Schedule的startDate早于 new Date(),并且调度周期又触发于startDate和new Date()之间时,就会立即触发当前job。简单的解决方式是将startDate设为new Date().

posted @ 2006-01-23 00:52 兼听则明 阅读(415) | 评论 (0)编辑 收藏

使用abator自动生成ibatis代码的经验 及碰到的问题的解决方案


1:abator下载:http://ibatis.apache.org/abator.html
2:将abator安装到eclipse中
3:此时可以新建一种文件类型:Abator for iBATIS Configuration File,建立一个
4:在 jdbcConnection 中设置要mapping的数据库的jdbc连接
  classPathEntry 是你的jdbc driver类路径
5:javaModelGenerator,sqlMapGenerator,daoGenerator 分别设置 java dataObject、sql mapping文件和 DAO 接口、实现类的生成位置:targetPackage 目标包,targetProject:eclipse项目
6:daoGenerator 中可以设置属性  type: ibatis 或 spring 指定生成的dao实现类是使用com.ibatis.dao.client.template.SqlMapDaoTemplate
还是
org.springframework.orm.ibatis.support.SqlMapClientDaoSupport
7: table 中 tableName 指定要处理的表名
  可以有多个table
8:table中可以包含子元素 generatedKey: 使Insert方法可以返回值--由指定的column mapping
9:generatedKey中的sqlStatement属性可以是获取sequence的SQL,也可以是获取自增值的SQL
  比如:Oracle的 select theSequence.nextVal from dual
       SQLServer的 SELECT @@IDENTITY as  column_name
10:保存文件,选中文件,右键菜单选择Generate iBATIS Artifacts! ok...



使用abtor生成的iBatis代码出现xml解析错误的解决方案
如果按上述方式生成的代码有xml解析错误:  请下载这个

注意,该文件名为Abator.rar.txt实际是一个rar文件,只是上传服务器有文件类型限制 所以只好加了扩展名txt。
请去掉.txt后解压。

使用
org.apache.ibatis.abator.core_0.5.1.jar
替换调你的 eclipse\plugins 的同名文件 即可。

然后重新生成代码。 OK 应该可以咯....

我改了一点代码,需要可以留言。

posted @ 2006-01-21 13:51 兼听则明 阅读(9061) | 评论 (12)编辑 收藏

在eclipse-plugin开发中碰到的怪问题:(eclipse 3.1.1 + wtp1.0)

一个popupMenus Extensions,
objectContribution:objectClass*: org.eclipse.core.resources.IFile

在action的Class代码中:
public void selectionChanged(IAction action, ISelection selection) {
    StructuredSelection ss = (StructuredSelection) selection;
        this.selectedFile==(IFile)ss.getFirstElement(); //此处抛出异常
}

上述代码的异常非常奇怪:
根据的的跟踪,ss.getFirstElement()返回值是File,该类实现了IFile接口,
而且我用 ss.getFirstElement().getClass().isAssignableFrom(IFile.class)返回是false;
真是奇怪!---有人知道为什么吗?

另外在实践eclipse plugin开发过程中也有几个心得:(肯定能用,但未必最佳)

1、如果开发plugin,所有的依赖库都要包含到 Plug-in Dependencies 中;而不能只是引入到工程中。
2、如何输出到console:
MessageConsole mc=new MessageConsole("****",null);
IConsole[] cs=new IConsole[1];
cs[0]=mc;
ConsolePlugin.getDefault().getConsoleManager().addConsoles(cs);
mc.activate();
PrintStream out=new PrintStream( mc.newOutputStream());
out.println("*******.");
3、如何获取依赖工程的输出路径:
selectedProject:当前工程---由用户选择
String[] ps= selectedProject.getRequiredProjectNames();                
IWorkspace w= selectedProject.getProject().getWorkspace();
for(int i=0;i<ps.length;i++){
IResource r=w.getRoot().findMember(ps[i]);
try{
    IJavaProject jp=new JavaProject((IProject)r,null);                
    File source=new File(jp.getProject().getLocation().append(jp.getOutputLocation().removeFirstSegments(1)).toOSString());
        //作你的事情.....
}catch(Exception e){
      //不是javaProject                                
    e.printStackTrace();                            
}                
4、如何使用进度Dialog:
Shell shell = new Shell();
ProgressMonitorDialog dialog = new ProgressMonitorDialog(shell);        
IRunnableWithProgress thread = new SomeRunner(shell);            
dialog.run(true, false, thread);
//=============================
private class SomeRunner implements IRunnableWithProgress {
    public void run(IProgressMonitor monitor)throws InvocationTargetException, InterruptedException {
    monitor.beginTask("一些信息", 数值-总工作量);
        for(;;){
        // 一些工作
        monitor.worked(数值-已完成工作量); //实际中,我得情况不太相符,不明白,但差不多 :(
        monitor.setTaskName("一些信息");
     // 一些工作    
       }
        monitor.done();
    }
}


posted @ 2006-01-20 18:45 兼听则明 阅读(587) | 评论 (0)编辑 收藏