论坛首页 Java企业应用论坛

一个自制持久层的方法

浏览 14278 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2004-08-26  
to ajoo
为什么要设计Record这样一个接口?看不出它存在的意义。

ajoo 写道

在这个层上面,还可以制作一个可选的对用户更友好的层,这一层,使用那几个缺省提供的RecordListener实现

这一层会怎么实现?似乎template似乎是个不错的选择。
spring对于jdbc的包装提供了很多层次上的抽象,记得有个中文网站分析了spring的jdbc的实现,写得不错。
0 请登录后投票
   发表时间:2004-08-26  
问题1。
如果不用record接口,把ResultSet直接给客户提供的listener,有几个缺点:
1。使用jdbc的细节对客户暴露。万一我以后底层换成了xml db,客户代码改不?
2。ResultSet接口里面给用户的控制太多。万一用户调用了rset.close()怎么办?万一他rset.movePrev()了又怎么办?

这个Record接口目的是提供一个最小化的,对底层实现独立的结果纪录的抽象。它是只读的,用户不可能给框架捣乱。它也是独立于物理层实现的,所以万一以后更换物理层,客户代码不需要改。

问题2。为了避免文章过长,我在这点上只是蜻蜓点水了。举个例子。先提供一个把结果集populate到list中去的listener实现:
interface ObjectFactory{
  Object transform(Record rec);;
}
final class ListPopulator implements RecordListener{
  private final ObjectFactory factory;
  private final ArrayList list = new ArrayList();;
  public bool receive(Record rec);{
    list.add(factory.transform(rec);;
    return true;
  }
  public List getResult();{return list;}
  ListPopulator(ObjectFactory f);{
    this.factory = f;
  }
}

这样,想把结果集储存到某一个list里的客户,可以用这个预先提供的ListPopulator。

再做一个只取一个结果的实现:
class RetrieveOne implements RecordListener{
  public void receive(Record rec);{
    ret(factory.transform(rec););;
    return false;
  }
  private void ret(Object obj);{
    result = obj;
  }
  private Object result;
  private final ObjectFactory factory;
  public Object getResult();{return result;}
  RetrieveOne(ObjectFactory f);{
    factory = f;
  }
}
当然,这还是要让傻瓜用户关心listener这个底层的接口。可以在上面再封装一层:
仍然是一个独立于具体数据库的接口先行
interface Persistence{
  List retrieveAll(Query qry);;
  Object retrieveOne(Query qry);;
}


对了,说到这,忘了提了,前面的那个使用RecordListener的功能应该也有个抽象的接口,可以这样写:
interface Db{
  void retrieve(Criteria cri, RecordListener rl);;
}

前面写的应用jdbc的那段嵌套try-finally代码就出现在它的jdbc实现类中 (比如叫它JdbcDb好了,如果需要,还可以有XmlDb等)。
值得说明的是,这里有一个Criteria对象,它就是纯粹描述查询逻辑的(比如我要出生日期在白垩纪以前的奥林匹克冠军之类的)。和Persistence层的Query对象不同,它不包含对象工厂的信息,我们在db层只关注Record就好了。
(你也看到了,遇到语义有微妙不同的相似功能的时候,我总是在起名字上很伤脑筋)


这个Persistence的实现也是独立于数据库的,你ioc进来一个Db的实例如下:
final class PersistenceImpl implements Persistence{
  private final Db db;
  public List retrieveAll(Query qry);{
    final ListPopulator lp = new ListPopulator(qry.getObjectFactory(););;
    db.retrieve(qry.getCriteria();, lp);;
    return lp.getResult();;
  }
  public Object retrieveOne(Query qry);{
    final RetrieveOne ro = new RetrieveOne(qry.getObjectFactory(););:
    db.retrieve(qry.getCriteria();, ro);;
    return ro.getResult();;
  }
  private PersistenceImpl(Db db);{
    this.db = db;
  }
  public static Persistence instance(Db db);{
    return PersistenceImpl(db);;
  }
}

呵呵,又是静态工厂。在一个类的唯一目标就是实现一个接口的时候,我是偏向应用静态工厂的。而如果类有些别的事情要做,比如那个RetrieveOne和ListPopulator,就不用静态工厂这么麻烦了,反正你不能直接返回接口。

在一些分层系统的设计中,上一层总是直接耦合下一层的实现,我觉得那是不好的。
所以,这个PersistenceImpl还是通过ioc来依赖db层。

这样,如果以后db层的实现更换了,persistence层的东西仍然不用动。

另外,这里的persistence层其实是一个可选的对db层的补充。客户代码是有权利选择绕过persistence层直接和更灵活的db层打交道的。

这里,因为只关心的是查询,所以没有事务处理的踪影。
另外,从Criteria对象转化到sql语句的逻辑应该出现在用jdbc实现db层的代码中,就是一个o-r映射,可以复杂,也可以简单,看你想把摊子铺多大了。这里也省略了。
ObjectFactory则涉及到实体类。仍然是省略了。
我省略这些也是为了突出重点。而且,这些东西相对来说和我现在给的设计方案是很正交的,不会互相影响。
0 请登录后投票
   发表时间:2004-08-26  
凤舞凰扬
看了你提供的代码,感觉这些都可以用hibernate实现!
另外,对于你查询返回的集合类,这些集合类对于lazy的支持,以及cache,对象级联关系 等等这些基本持久层概念,你都没较深入提及,不知道你的设计是否考虑了这些咚咚!如果没有考虑或者没有实现,那么你的持久层只能算是一个sql和事务管理模板生成器。
0 请登录后投票
   发表时间:2004-08-26  
问题1,我的意思是record可以直接是个实体类啊,不需要再加一个实现了,没有看到它将来会发生的变化。这样的话,使用静态方法对我来说是自然而然的,可它偏偏是接口。如果真考虑到将来的变化,那么代码中使用final Record rec = ResultSet2Record.adapt(rset); 对我来说不可接受,宁愿用一个工厂类,也可以使用ioc,这时候ioc和工厂类在实现角度上是可以替换的。这也是我的观点,在某种程度上ioc的使用会降低工厂类的使用。

问题2
RecordListener的子类有很多,且还有很多类似代码,比如factory属性,这时,我会考虑增加一个抽象类,方便子类的编写者。我希望我的架构不仅方便使用者,也方便扩展者。虽然有潜在被继承者滥用的风险。
最后没有明白的是
ajoo 写道

呵呵,又是静态工厂。在一个类的唯一目标就是实现一个接口的时候,我是偏向应用静态工厂的。而如果类有些别的事情要做,比如那个RetrieveOne和ListPopulator,就不用静态工厂这么麻烦了,反正你不能直接返回接口。

在一些分层系统的设计中,上一层总是直接耦合下一层的实现,我觉得那是不好的。
所以,这个PersistenceImpl还是通过ioc来依赖db层。

这样,如果以后db层的实现更换了,persistence层的东西仍然不用动。

1,你给出的PersistenceImpl的代码能ioc吗?
2,能的话,怎么做到的?
3,不能话,是否意味着不能再使用静态工厂了,这两者矛盾的时候,你会选择哪一个。我会毫不留情的扔掉静态工厂,哦,静态工厂又少了一个。
0 请登录后投票
   发表时间:2004-08-26  
firebody 写道
凤舞凰扬
看了你提供的代码,感觉这些都可以用hibernate实现!
另外,对于你查询返回的集合类,这些集合类对于lazy的支持,以及cache,对象级联关系 等等这些基本持久层概念,你都没较深入提及,不知道你的设计是否考虑了这些咚咚!如果没有考虑或者没有实现,那么你的持久层只能算是一个sql和事务管理模板生成器。

     呵呵,楼上,我写的和hibernate当然不能比的. 不过像你说的lazy一般在级联的时候(也就是存在一对多或多对多关系的时候)才会存在.我说了,暂时还没有实现关系的映射,所以算不上什么完全意义的ORM.没有关系映射,那么自然也就不存在lazy装载了(唉,我是没有想清楚怎么个实现好)
    至于cache,当然是用到了. 其实我就是写的一个sql map,和ibatis的相似,同时可以结合自己的Page,事务管理,对象版本控制(用于判别脏数据). 不过,倒不是模板生成器,只是想简化一些工作.
     等将来我对hibernate熟悉了,项目组自然就用hibernate了.
0 请登录后投票
   发表时间:2004-08-26  
我的一个新想法(其实也不是完全是自己想的,很多网友提供了思路), domain对象可以做成接口,而实体, VO则实现这个接口,这样就避免了以前经常提到的实体映射数据库字段名字或者类型修改所带来的影响以及采用VO存在多次对象赋值这样的概念冲突.
     只不过,我还在试验中,要构思一个比较好的开发模式和思路才好
0 请登录后投票
   发表时间:2004-08-26  
youcai 写道
问题1,我的意思是record可以直接是个实体类啊,不需要再加一个实现了,没有看到它将来会发生的变化。这样的话,使用静态方法对我来说是自然而然的,可它偏偏是接口。如果真考虑到将来的变化,那么代码中使用final Record rec = ResultSet2Record.adapt(rset); 对我来说不可接受,宁愿用一个工厂类,也可以使用ioc,这时候ioc和工厂类在实现角度上是可以替换的。这也是我的观点,在某种程度上ioc的使用会降低工厂类的使用。

问题2
RecordListener的子类有很多,且还有很多类似代码,比如factory属性,这时,我会考虑增加一个抽象类,方便子类的编写者。我希望我的架构不仅方便使用者,也方便扩展者。虽然有潜在被继承者滥用的风险。
最后没有明白的是
ajoo 写道

呵呵,又是静态工厂。在一个类的唯一目标就是实现一个接口的时候,我是偏向应用静态工厂的。而如果类有些别的事情要做,比如那个RetrieveOne和ListPopulator,就不用静态工厂这么麻烦了,反正你不能直接返回接口。

在一些分层系统的设计中,上一层总是直接耦合下一层的实现,我觉得那是不好的。
所以,这个PersistenceImpl还是通过ioc来依赖db层。

这样,如果以后db层的实现更换了,persistence层的东西仍然不用动。

1,你给出的PersistenceImpl的代码能ioc吗?
2,能的话,怎么做到的?
3,不能话,是否意味着不能再使用静态工厂了,这两者矛盾的时候,你会选择哪一个。我会毫不留情的扔掉静态工厂,哦,静态工厂又少了一个。

record当然有其它的实现了。现在就是一个ResultSet2Record,明天可能有Dom2Record,后天可能有PrevalerRecord,
这个设计的目的既然是屏蔽底层的实现,那么自然就要考虑到底层不是jdbc的情况。
另外,那个通过封装防止用户误操作的理由我认为也是很强的。

那句
ResultSet2Record.adapt(rset);
出现在用jdbc对Db接口的实现,Dom2Record.adapt(dom)可能出现在xml db对Db接口的实现。
此时你要求ioc是不现实的。ioc注射进来的是跟你本身的逻辑正交的,没有直接耦合的东西。
你能想象给JdbcDb注射一个Dom2Record的景象吗?
另外,这里很多人有一个误会:
OO的原则是单向依赖,不是没有依赖。因为天然就互相依赖的东西不可能没有依赖。
你们总觉得用了配置文件就没有依赖了,不是的,依赖只不过跑到了配置文件中。而这个依赖不见得总是最合适放在配置文件里。


关于ioc,我发现还有一个误解:用构造函数的才算是ioc,用工厂的就不算。
这个误解我想来自于最初的pico等容器不支持工厂。
进而,这又导出了我一直要说明的一点:ioc就是ioc,不是容器。不是说你使用了容器才叫ioc。只要你把和你的实现正交的逻辑用接口抽象并且从外界接受进来,就是ioc了。

在这个例子,你可以手工在外面一个组装代码处调用PersistenceImpl.instance(JdbcDb.instance());
这也是ioc。
如果你有个更友好的容器, 支持工厂的话, 你可以用配置文件来组装。
或者,用Preference api自己写一个组装也不难。
pico本身也没有配置文件的,如果不用那些增值库,直接用pico的话,你也要手工在java代码里组装。难道那就不叫ioc了?

总之,怎么组装是另一个问题,和是否ioc无关。
0 请登录后投票
   发表时间:2004-08-27  
问题1,如果db和record是互相依赖的,确实不适合ioc这种做法。ioc中配置文件解决的是实现和接口的分离。db一定和对应的recotd的实现配对,这个依赖无论如何也打不破,可如果db只需要知道record的接口,这时就需要ioc了,至于使不使用配置文件是无所谓的。对于这里是否应该紧密耦合,不属于这个话题。

问题2,我知道分歧所在了
ajoo 写道

在这个例子,你可以手工在外面一个组装代码处调用PersistenceImpl.instance(JdbcDb.instance());
这也是ioc。
。。。
只要你把和你的实现正交的逻辑用接口抽象并且从外界接受进来,就是ioc了。

如何组装组件是分歧点,正如在Martin Fowler的文章中说到的,我倾向于
Dependency Injection这种方式并配合spring这类ioc容器。你是无所谓的,既可以采用和我相同的思路,也可以利用ServiceLocator(实际上也就是一个工厂类)来完成组件组装。
我同意只要PersistenceImpl做到了和db的具体实现分离,就是ioc了。但不能算Dependency Injection(至少按照Martin Fowler的文章看是这样)。
pico因为支持ioc所以被成为ioc容器,它有一套自己组装代码的方式,也因此有了一些限制,比如只有ioc三种类型。是接受这些限制来使用它,还是通过其他形式来完成组件的组装是设计的决策。
对于配置文件和ioc的关系,我这样认为,在ioc里配置文件是一个不错的实现手段,仅此而已。使用ioc容器能方便的组装组件,仅此而已。
0 请登录后投票
   发表时间:2004-08-27  
1. 这里, 每个XXXDbImpl都是一个Record的提供者而不是使用者, 使用Record对象的是用户提供的RecordListener.
而提供者总是要和被提供的东西耦合的.你实现一个抽象工厂,总要知道new哪个类吧? 你ioc, 组装者总要知道调用哪个构造函数吧?
这里的ResultSet2Record完全可以看成是jdbc把自己adapt到一个抽象接口上. JdbcDb本来就需要实现一个Record. 你让它再ioc进来, 那谁来实现啊? 总不能让大家都ioc, 没人实现吧?
所以我向来对提供者直接依赖具体不在乎, 总有东西要依赖具体的. 容器也罢,配置文件也罢. 只要它们本身是有耦合关系的,而不是正交关系, 依赖就依赖. 理直气壮.

2. 叫什么无所谓. 没有容器时我是这么写, (当时还不知道这叫ioc, 就是觉得应该这么写), 有容器时我也不会变.


另外, 题外话, 关于理想和现实的冲突.

在那个静态厂的帖子里, 我相信很多人反对静态厂都是因为有些容器不支持.

这是一个很现实的反对.

怎么说呢? 理论上, 我们可以说: 容器不支持是容器傻, 这么好的东西为什么不支持. 但是, 实际上所谓谁的腰粗你就抱谁. 让一个开发者选择: 你是准备为了美好理想, 用静态工厂, 然后和pico们打官司或者自己写一个容器? 还是自己委屈点, 接受容器的限制, 然后利用容器来帮助自己获得更大的利益?

我想多数人, 包括我, 遇到真正的利益问题, 都会选择后者. (虽然我心里还会咒骂pico弱智)

这个例子其实也是, 从api设计的角度, 从一个对容器一无所知的角度, 我可能是觉得静态工厂提供的灵活性很爽.
但是, 如果真正做起项目来, 如果最终的对象组装任务很繁重, 如果pico对我的唧唧歪歪当作耳旁风, 如果不是很有时间自己写一个容器, 那么最终也许还是会向现实低头.

当然, 如果如readonly所说, pico支持静态工厂了, 自然这一切冲突就不存在了.


不过有一点我要强调的是: 控制的意义.
我写程序喜欢开始的时候用最强的控制. 一个东西, 能私有就私有, 能package私有就package 私有, 能final就final,能返回父类或者接口的, 绝不返回子类或者实现接口的类.
我的逻辑是:从private变成public容易, 从final变成non-final容易, 从返回接口变成返回类容易, 反过来就基本不可能.
所以, 当我发现因为一些客观条件的限制, 或者因为客户端合理的要求, 一个东西要公有, 或者一个类要允许继承, 我再去放开这个限制.

从这个意义上来说, 如果我开始不是完全确定我的类一定需要公有构造函数, 我不会让它公有的. 我宁可最后再改, 也不愿开始的时候放松控制.

我不象我们这里的一些专家们神经那么脆弱,对改动代码那么敏感.
我相信这句话: nobody get it right the first time.

即使你是大牛, 我也不相信你不需要改动代码.一次就可以搞定, 那是神仙, 不是人.

所以, 为了以后改动代码的方便, 我会采用一些技术让我改动代码的时候对外界的影响最小. 让我第一次, 第二次, 第三次 ... 犯的错误代价最小.
0 请登录后投票
   发表时间:2004-08-27  
1,是啊,既然是依赖实现了,如果还要去ioc就是脱裤子放屁。当讨论利用配置文件来完成ioc时,根本不适合你这种情况。
ajoo 写道
2. 叫什么无所谓. 没有容器时我是这么写, (当时还不知道这叫ioc, 就是觉得应该这么写), 有容器时我也不会变.
。。。
我想多数人, 包括我, 遇到真正的利益问题, 都会选择后者. (虽然我心里还会咒骂pico弱智)

这个例子其实也是, 从api设计的角度, 从一个对容器一无所知的角度, 我可能是觉得静态工厂提供的灵活性很爽.
但是, 如果真正做起项目来, 如果最终的对象组装任务很繁重, 如果pico对我的唧唧歪歪当作耳旁风, 如果不是很有时间自己写一个容器, 那么最终也许还是会向现实低头.

2,完全同意你的观点,只是说了一大堆,还是不知道你的代码是不是现实的选择。
这类轻量级的容器出现会改变我们很多编码的习惯和方式,就如出现了好的组件,会改变我们对组件使用的态度。比如,对于log我们都是直接静态创建了,很少有人会再考虑自己实现一套log的接口,以备将来替换log的具体实现。
如果我碰到你这样的需求,而spring或者其他组件完成了类似功能,我会毫不客气拿来就用,这也是我对好的容器的态度。
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics