自从有了接口的概念后,OO编程都推荐面向接口编程。根据“如非必要,勿增实体”的原则,通常我们定义(或重构出来)的接口都是有行为的,很少用空接口。那么空接口有什么用呢?
一个接口定义了两方面,类别和特征。比如
public interface Animal{ void eat(); void sleep(); }
定义了类别Animal,它的特征是:可以eat和sleep。如果我们不关心sleep,这个接口就变成
public interface Animal{ void eat(); }
这两个Animal描述的是现实中的同一类别,只是关心的角度不同,抽取出的特征也不同。
更进一步,如果连eat这个特征我们都不关心,这个接口就变成了一个空借口
public interface Animal{ }
定义这样一个借口相当于阐述以下事实“尽管现在不关心动物具体有什么特征,但本接口的存在说明系统中动物和非动物是有区别的,以后也可能会用到这些区别”。
这样,Tree不用实现Animal,但Dog应该实现Animal。空命名接口的存在对类的设计给出了约束,即方便了以后扩展,也让后来者理解代码更加方便。可以认为是“代码即文档”的一种体现。
-------------------------------------------------------------------------------------------------------------------------
在通常的业务系统中,分页查询是非常常用的功能。对这样的查询,在用Toplink(Hibernate应该也一样)实现时一个最佳实践是不用对象,而用SQL实现。
假设User包括id,name,birthday,gander,department,isActive几个属性,在“维护用户”界面上需要分页显示id,name,birthday,gander。那么DAO不要返回一个Page对象给Service,而应该自定义一个PagingUserResult
public class PagingUserResult{ private String id; private String name; private Date birthday; private Gander gander; //.. Getter and Setter }
并返回Page<PagingUserResult>给Service。这是因为
1)Toplink组装对象的时间比较长,特别是如果在UnitOfWork中作这件事,每个User会被Clone一份到UnitOfWork中,整体时间消耗要多不少。
2)分页功能牵涉的数据量是比较大的,如果User组装成对象,一旦翻上几页,这些实际不会操作到的对象就会把缓存中的User都挤出缓存,使得缓存命中率非常低,使得用缓存不如不用。
所以,设计框架时要求分页查询都用ReportQuery,不允许出现直接返回Page<User>给Service的情况。当然,这样的规范必须通知Service的实现者。这可以通过文档,code review等方式,不过最好的方法是在框架中尽量杜绝Service这样作的可能性,这时候空接口就派上作用了。
首先定义一个空接口
public interface QueryResult{ }
由于具体的QueryResult(比如PagingUserResult)就是个Bean,所以QueryResult没有共同行为,只是一个标识作用。
然后,定义Page接口
public interface Page<T extends QueryResult> { T get(int index); //.... other functions }
这里,通过泛型表达了“Page只能容纳QueryResult”,由于User是Domain对象,不会实现QueryResult,自然不可能被放到Page中了。
当然,由于Page只是一个接口。为了使用方便,框架还要提供一个PageBuilder,这里是一个大概的样子
public class PageBuilder<T extends QueryResult> { private List<T> contents=new ArrayList<T>(); public PageBuilder<T> add(T content) { contents.add(content); return this; } public Page<T> buildPage(){ // You may need a inner class which implements Page<T> here } }
总结一下,结合空接口QueryResult和泛型,我们避免了developer以后由于不了解而误用的情况。
类似的,也可以让Domain对象实现一个叫作Entity的空接口,可以规范DAO的使用,避免像DAO中传入非Domain的Class。