对于一个常常和代码打交道的人来说,代码重用是非常重要的。在我们开发系统时免不了会经常写些功能极其类似的代码,而这些代码若不加以管理便会使得程序变得十分臃肿,让后期的维护变得非常痛苦。软件工程师的一个目标就是通过重复使用代码来避免编写新的代码。这样做并不是因为他们懒,而是因为重新使用已有的代码可以降低成本、增加代码的可靠性并提高它们的一致性。
在SSH(Struts + Spring + Hibernate)的开发中尤其如此。在刚刚开始学SSH架构的时候,会发现经常要写一大堆雷同的代码,每个Action都如此雷同(都是什么图表呀、查询呀、翻页呀),每个Dao都是如此相似(都是什么get呀、save呀、delete呀),让人写代码的时候烦不胜烦。这么多相同的字段,类似的方法,如果在每个类中都写一次,无疑是件让崩溃的事(上个星期我还是这么做的,痛苦)。
如果在写程序时注重代码的重用,不但可以大大地提高开发效率,也让后期的修改维护轻松很多(千万别以为代码重用是复制粘贴~)。学会面向接口编程和善于利用合理的继承,才是真正的做到代码的重用。
前几天看到了一个SSH的开源应用——Micrite(http://code.google.com/p/micrite/),拿来当学习范例实在是不错,它的代码写的很漂亮(起码对我来说是的),让我学到了不少代码重用的知识(这些东西在书上应该是很难学到的)。
首先,是面向接口编程(这个就不多说啦,很好懂)。
其次,是善于利用合理的继承(记住是“合理”的基础,因为一般java的继承是有害的,见http://niyunjiu.iteye.com/blog/321401)。
例如说,我们可以让让所有的dao都继承置一个抽象类GenericDAOImpl,利用泛型编程将在Dao层常用的方法都抽象出来,这样就不用每次都痛苦的写get、save、update、delete了。利用泛型编程,实在是件大快人心的事(之前用那些List、Iterator的泛型感觉很爽,现在才发现原来自己也可写泛型编程,哈哈)。
//以下是Micrite中的GenericDAOImpl
public abstract class GenericDAOImpl<T,ID extends Serializable>
extends HibernateDaoSupport implements IGenericDAO<T, ID> {
private Class<T> persistentClass;
//泛型编程,以后直接继承就可以省写很多重复代码了
@SuppressWarnings("unchecked")
public GenericDAOImpl() {
this.persistentClass = (Class<T>) ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0];
}
public Class<T> getPersistentClass() {
return persistentClass;
}
public void save(T entity) {
getHibernateTemplate().save(entity);
}
public void update(T entity) {
getHibernateTemplate().update(entity);
}
public void delete(T entity) {
getHibernateTemplate().delete(entity);
}
@SuppressWarnings("unchecked")
public T get(ID id) {
return (T) getHibernateTemplate().get(getPersistentClass(), id);
}
}
又例如说,我们可以让所有用到分页、图表、查询的Action都继承自一个基类GenericAction,用来包含有分页信息,自定义查询等信息,有了这种方法就不用为分页而感到烦恼了(如果每个Action都写进这些分页信息,那真是敲键盘敲到手软)。
//以下是Micrite中的GenericAction,
public class GenericAction extends ActionSupport {
private static final long serialVersionUID = -6554298643237377735L;
//以Map格式存放操作的结果,然后由struts2-json插件转换为json对象
private Map<String, Object> resultMap = new HashMap<String, Object>();
// 分页起始索引
private int start;
// 分页限制数
private int limit;
// 分页记录总数(分页中改变页码时,会传递该参数过来)
private int totalCount;
private int chartWidth = 600;
private int chartHeight = 450;
//用来保存用户自定义查询信息,也是一个代码重用的好方法,稍后再说
private SearchBean[] queryBean;
public boolean isFirstSearch() {
return totalCount == 0;
}
@SuppressWarnings("unchecked")
public void putResultList(List data) {
resultMap.put("totalCount", totalCount);
resultMap.put("success", true);
resultMap.put("data", data);
}
public void putChartResultList(JFreeChart chart) {
StandardEntityCollection entityCollection = new StandardEntityCollection();
ChartRenderingInfo info = new ChartRenderingInfo(entityCollection);
String filename = "";
try {
filename = ServletUtilities.saveChartAsPNG(chart, getChartWidth(), getChartHeight(), info, null);
String mapName = "map" + new Date();
String mapInfo = ChartUtilities.getImageMap(mapName, info);
resultMap.put("success", true);
resultMap.put("filename", filename);
resultMap.put("map", mapInfo);
resultMap.put("mapName", mapName);
} catch (IOException e) {
resultMap.put("success", false);
}
}
//省略Setter and Getter部分
//没有setQueryBean(),而是利用queryString来构造queryBean[],好方法呀
public void setQueryString(String queryString) {
/*SearchFactory类可根据一个String来生成queryBean,
还可用来生成Hibernate用到的DetachedCriteria,
大大简化了查询过程*/
this.queryBean = SearchFactory.getSearchTeam(queryString);
}
}
下面简单说说SearchBean,它是用来保存自定义查询的相关数据的类,结构很简单:
public class SearchBean {
//字段名称
private String name;
//字段值
private String value;
//条件关系,包含:=, <, >, <=, >=, like, in
private String relation;
//省略Setter and Getter
}
这样封装了查询的数据后可以使开发中查询过程所需的数据更方便处理,在写查询方法的时候,只需提供Class和SearchBean的参数就可以了,一个方法就可以实现多种查询功能,而且让代码更可靠更统一了(又少写了一堆代码,哈哈,不知道这种算不算重用呢?)。
下面再看看在Micrite中SearchFactory类的两个主要方法(它可将自定义查询的值封装为SearchBean数组,并动态生成Hibernate查询时所需的DetachedCriteria对象实例)
public class SearchFactory {
/**
* @param scarchBunch 格式:
* [name,value,relation],[name,value,relation],[name,value,relation]
* name: 字段名称
* value: 字段值
* relation:条件关系,包含:=, <, >, <=, >=, like, in
* @return
*/
public static SearchBean[] getSearchTeam(String scarchBunch) {
//略
}
/**
* @param entity 查询的对象类型
* @param searchBean 查询条件
* @return DetachedCriteria对象实例
*/
public static DetachedCriteria generateCriteria(Class entity, SearchBean[] searchBean) {
//略
}
}
后记:我实在不想写Struts那些什么校验、国际化、标签之类的(到处都找的到的东西)作为学习笔记,我只想随手写写学习中的想法,把一些Java编程中的一些有用的思想记录下来(我相信Struts没多久就会过时的,我看好JSF)。