重构实战案例一,以子类取代类型编码(原创)
以子类取代类型编码
Replace Type Code with Subclasses
1. 何谓重构
1.1名词解释
对软件内部结构的㆒种调整,目的是在不改变「软件之可察行为」前提下,提高其可理解性,降低其修改成本。
1.2动词解释
使用一系列重构准则(手法),在不改变「软件之可察行为」前提下,调整其结构。
2. 为何重构
2.1「重构」改进软件设计
同样完成一件事,设计不良的程序往往需要更多代码,这常常是因为代码在不同的地方使用完全相同的语句做同样的事。因此改进设计的一个重要方向就是消除重复代码(Duplicate Code)
2.2「重构」使软件更易被理解
你的源码还有其它读者:数个月之后可能会有另一位程序员尝试读懂你的代码并做一些修改。我们很容易忘记这第二位读者,但他才是最重要的。计算器是否多花了数个钟头进行编译,又有什么关系呢?如果一个程序员花费一周时间来修改某段代码,那才关系重大— 如果他理解你的代码,这个修改原本只需一小时
2.3「重构」助你找到臭虫 ( bugs)
Kent Beck 经常形容自己的㆒句话:『我不是个伟大的程序员;我只是个有着㆒些优秀习惯的好程序员而已。』重构能够帮助我更有效地写出强固稳健(robust)的代码。
2.4「重构」助你提高编程速度
终于,前面的一切都归结到了这最后一点:重构帮助你更快速地开发程序。听起来有违反直觉。当我谈到重构,人们很容易看出它能够提高质量。改善设计、提升可读性、减少错误,这些都是提高质量。但这难道不会降低开发速度吗?我强烈相信:良好设计是快速软件开发的根本。事实上拥有良好设计才可能达成快速的开发。如果没有良好设计,或许某一段时间内你的进展迅速,但恶劣的设计很快就让你的速度慢下来。你会把时间花在调试上面,无法添加新功能。修改时间愈来愈长,因为你必须花愈来愈多的时间去理解系统、寻找重复代码。随着你给最初程序打上一个又一个的补丁(patch),新特性需要更多代码才能实现。真是个恶性循环。
3.何时重构?
重构本来就不是一件「特别拨出时间做」的事情,重构应该随时随地进行。你不应该为重构而重构,你之所以重构,是因为你想做别的什么事,而重构可以帮助你把那些事做好
3.1三次法则(The Rule of Three)
Don Roberts 给了我一条准则:第一次做某件事时只管去做;第二次做类似的事会产生反感,但无论如何还是做了;第三次再做类似的事,你就应该重构。
☆ 事不过三,三则重构。(Three strikes and you refactor.)
3.2重构时机
添加功能时一并重构
修补错误时一并重构
复审代码时一并重构
-以上章节摘抄自《重构-改善既有代码的设计》
4.平台重构案例
4.1重构动机
1. 实例模块为View,重构元素为ViewAction、ViewProcessBean、View,以下为关系图。
2. 由于View存在多种editMode(编辑模式),而每个调用的地方都需要进行type code(类型码)判断,然后再进行相应的业务逻辑处理,最终在每个调用的地方都形成了大量的if-else代码,大大减弱了代码的可读性,和逻辑清晰度。
3. 调用的地方:
4.2重构作法
如上图所示,将每种type code重构成subclass,加强了每种类型处理业务逻辑的能力。
View-版本1566代码片段:
public EditMode getEditModeType() {
if (EDIT_MODE_CODE_DQL.equals(getEditMode())) {
return new DQLEditMode(this);
} else if (EDIT_MODE_CODE_SQL.equals(getEditMode())) {
return new SQLEditMode(this);
} else if (EDIT_MODE_DESIGN.equals(getEditMode())) {
return new DesignEditMode(this);
}
return new NullEditMode(this);
}
说明:调用者无需了解具体的类型,由View自身作判断,返回EditMode接口,从而实现多态调用。
ViewProcessBean代码片段:
重构前-版本1503:
public String expDocToExcel(String viewid, WebUser user, ParamsTable params) throws Exception {
if (view.getEditMode().equals(View.EDIT_MODE_DESIGN)) {
datas = dp.queryBySQLPage(sql, params, tempPage, LINES, user.getDomainid());
} else if (view.getEditMode().equals(View.EDIT_MODE_CODE_DQL)) {
datas = dp.queryByDQLPage(dql, params, tempPage, LINES, user.getDomainid());
} else if (view.getEditMode().endsWith(View.EDIT_MODE_CODE_SQL)) {
datas = dp.queryBySQLPage(sql, params, tempPage, LINES, user.getDomainid());
}
}
重构后-版本1566:
public String expDocToExcel(String viewid, WebUser user, ParamsTable params) throws Exception {
datas = view.getEditModeType().getDataPackage(params, tempPage, LINES, user, currdoc);
//其他业务逻辑
}
5.结语
由上述案例可看到,引入subclass代替type code可以大大减少if-else判断,而且可以把责任内聚到每种type中,使代码结构更清晰易懂。
6.其他
在本案例中引入了空类型概念,即NullEditMode,代码如下:
/**
*
* @author nicholas zhen
*
*/
public class NullEditMode extends AbstractEditMode implements EditMode {
public NullEditMode(View view) {
super(view);
}
public String getQueryString(ParamsTable params, WebUser user, Document sDoc) {
return "";
}
public DataPackage getDataPackage(ParamsTable params, WebUser user, Document doc) throws Exception {
return new DataPackage();
}
public DataPackage getDataPackage(ParamsTable params, int page, int lines, WebUser user, Document doc) throws Exception {
return new DataPackage();
}
public long count(ParamsTable params, WebUser user, Document doc) throws Exception {
return 0;
}
}
说明:空类型保证了调用每个方法都有默认值返回,而不需要进行非空判断,当View没有类型时,即返回默认的空类型
原创人员:Nicholas