副标题#e#
在已往一年的时间中,我在“ 追求代码质量 ” 专栏撰写了大量的文章。这 些文章向各人先容了很多可以改造代码质量的东西和能力。我已经向各人展示了 如何应用代码怀抱来监控代码库的质量;如何利用 TestNG、FIT 和 Selenium 之类的测试框架来检讨应用措施的成果;以及如何利用 XMLUnit 和 StrutsTestCase 之类的扩展框架(和一些成果强大的辅佐东西,如 Cargo 和 DbUnit)来扩展测试框架的应用范畴。
固然代码怀抱和开拓人员测试对付在整个开拓进程中确保代码质量很是重要 (就像我常常所说的,要实时并常常举办测试),可是它们根基上只能对代码质 量做出回响。您通过测试和怀抱代码来确定和量化代码的质量,可是代码自己都 已经写好了。岂论您做出多么尽力,城市受困于最初的设计。
虽然,差异的要领所设计出来的软件系统会有好有坏,良莠不齐。优秀设计 的要害因素之一就是留意保持系统的可维护性。粗劣设计的并可执行的系统大概 易于编写,可是要对它们提供支持确实是一个挑战。这些系统往往懦弱不堪,也 就是说对系统中某个区域的修改将会影响到其它看上去绝不相干的区域,因此要 对它们举办重构也相当的坚苦和耗时。向代码库中添加开拓人员测试可觉得我们 提供事情的筹划,可是其希望自己仍然是一个费力缓和慢的进程。
我们可以通过重构来改造已经编写好的代码,可是凡是来说在代码已完成之 后再举办窜改耗费庞大。而假如在一开始就把代码编写得 精细绝伦 会不会越发 利便和轻松呢? 这个月,我将先容一种很是主动的能力,可以确保软件系统的 质量和可维护性。依赖性倒置原则 被证明是编写可维护和可测试的高质量代码 的须要条件。依赖性倒置原则的根基思想就是工具应该依赖于抽象 而不是实现 。
是依赖性倒置 而不是依赖性注入
依赖性倒置原则与依赖性注入并没有直 接的干系。依赖性注入,也被称作节制反转(inversion of control,IOC),即 利用 Spring 之类的框架在运行的时候(而不是在编译的时候)链接工具的依赖 干系。固然依赖性倒置和依赖性注入并不需要同时利用,可是它们是互补的:两 个能力都力图操作抽象而不是实现。
过于细密的耦合
您大概至少传闻过面向工具编程中所利用的术语耦合(coupling)。耦合即 应用措施中各组件(或各工具)间的彼此干系。松散耦合的应用措施要比细密耦 合的应用措施更具模块化。松散耦合应用措施中的组件依赖于各类接口和抽象类 ,而细密耦合的系统则与之相反,其组件依赖于各类详细的类。在松散耦合的系 统中,其组件是利用抽象而不是 实现来彼此关系的。
假如有图解的话,可以很轻松地领略细密耦合的问题。举例说明,图 1 中的 软件系统的 GUI 与它的数据库相耦合:
图 1. 一个细密耦合的系统
GUI 对某个实现(而不是抽象)的依赖会对系统造成限制。在数据库未启动 和运行的环境下 GUI 是无法执行的。从成果的角度上看这种设计好像并不是很 糟糕 —— 究竟,我们一直都是这样编写应用措施并且也没有出什么问题 —— 可是测试就要另当别论了。
‘懦弱’ 的系统
图 1 中的系统使得断绝编程分外地坚苦,而这对测试和维护系统各个方面又 十分须要。您将需要一个具有正确查找数据的勾当数据库来测试 GUI,和一个运 行正常的 GUI 来测试数据会见逻辑。您可以利用 TestNG-Abbot(此刻的名称为 FEST)来测试前端,可是这样仍然无法汇报您任何有关数据库成果的内容。
清单 1 展示了这种糟糕的耦合。GUI 的一个特定的按钮界说了一个 ActionListener,它通过 getOrderStatus 挪用直接与底层数据库通信。
清单 1. 把 ActionListener 界说为 GUI 中的一个按钮
findWidgetButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
try {
String value = widgetValue.getText();
if (value == null || value.equals("")) {
dLabel.setText("Please enter a valid widgetID");
} else {
dLabel.setText(getOrderStatus(value));
}
} catch (Exception ex) {
dLabel.setText("Widget doesn't exist in system");
}
}
//more code
});
#p#副标题#e#
单击 GUI 的按钮组件后,直接从数据库中检索某个特定数令的状态,如清单 2 所示:
清单 2. GUI 通过 getOrderStatus 要领直接与数据库通信
#p#分页标题#e#
private String getOrderStatus(String value) {
String retValue = "Widget doesn't exist in system";
Connection con = null;
Statement stmt = null;
ResultSet rs = null;
try {
con = DriverManager.getConnection ("jdbc:hsqldb:hsql://127.0.0.1", "sa", "");
stmt = con.createStatement();
rs = stmt.executeQuery("select order.status "
+ "from order, widget where widget.name = "
+ "'" + value + "' "
+ "and widget.id = order.widget_id;");
StringBuffer buff = new StringBuffer();
int x = 0;
while (rs.next()) {
buff.append(++x + ": ");
buff.append(rs.getString(1));
buff.append("\n");
}
if(buff.length() > 0){
retValue = buff.toString();
}else{
retValue = "Widget doesn't exist in system";
}
} catch (SQLException e1) {
e1.printStackTrace();
} finally {
try {rs.close();} catch (Exception e3) {}
try {stmt.close();} catch (Exception e4) {}
try {con.close();} catch (Exception e5) {}
}
return retValue;
}
清单 2 中的代码呈现了问题,尤其是它通过一个硬编码的 SQL 语句直接与 一个硬编码的数据库举办通信。Yeeesh! 您可以或许想像开拓人员测试这种 GUI 和 相关数据库的挑战吗(顺便说一下,测试本应该简朴得像测试一个 Web 页面一 样)? 倘若对数据库的任何窜改都将 影响到 GUI,那么要思量修改系统的话会 使环境变得更糟。
DAO 模式
Data Access Object (DAO) 是一种设计模式,它旨在利用接口 和相关实现把初级的数据会见操纵从高级事务逻辑中疏散出来。从本质上说,某 个详细的 DAO 类通过特定的数据源实现会见数据的逻辑。DAO 模式使得只利用 一个接口为多个数据库,可能甚至各类差异的数据源(如文件系统)界说多个具 体实现成为了大概。
转变为松散耦合!
此刻在脑海中思量一下利用依赖性倒置原则设计的沟通的系统。如图 2 所示 ,通过向应用措施添加两个组件来清除应用措施中的耦合是大概的:这两个组件 别离是一个接口和一个实现:
图 2. 一个松散耦合的系统
在图 2 所示的应用措施中,GUI 依赖于一个抽象 —— 一个数据会见工具或 DAO。DAO 的执行直接依赖于数据库,可是 GUI 自己并没有陷入个中。以 DAO 的形式添加一个抽象可以从 GUI 实现将数据库实现解耦。一个接口会替代数据 库与 GUI 代码相耦合。清单 3 显示了该接口。
清单 3. WidgetDAO 是一个能辅佐解耦架构的抽象
public interface WidgetDAO {
public String getOrderStatus(String widget);
//....
}
GUI 的 ActionListener 代码引用接口范例 WidgetDAO(界说在清单 3 中) 而不是接口的实际实现。在清单 4 中,GUI 的 getOrderStatus() 要领在本质 上指定的是 WidgetDAO 接口:
清单 4. GUI 依赖于抽象,而不是数据库
private String getOrderStatus(String value) {
return dao.getOrderStatus(value);
}
对 GUI 完全埋没了这个接口的实际实现,因为它是通过一个工场来请求实现 范例的,如清单 5 所示:
清单 5. 对 GUI 埋没了 WidgetDAO 实现
private WidgetDAO dao;
//...
private void initializeDAO() {
this.dao = WidgetDAOFactory.manufacture();
}
希望顺利
留意,清单 5 中的 GUI 中的代码只引用接口范例 —— GUI 中的任那里所 都没有利用(或导入)接口的实现。这种对实现细节的抽象是机动性的要害:它 使您可以或许改换实现范例,而完全不会影响到 GUI。
还要留意,清单 5 中的 WidgetDAOFactory 是如何使 GUI 避开 WidgetDAO 范例的建设细节的。这些是工场的任务,如清单 6 所示:
清单 6. 工场对 GUI 埋没了实现细节
public class WidgetDAOFactory {
public static WidgetDAO manufacture(){
//..
}
}
使 GUI 引用对某个接口范例的数据检索可觉得建设差异的实现提供机动性。 在这种环境下,部件信息生存在数据库中,因此可以建设一个 WidgetDAOImpl 类与数据库直接通信,如清单 7 所示:
清单 7. WidgetDAO 范例的任务
public class WidgetDAOImpl implements WidgetDAO {
public String getOrderStatus(String value) {
//...
}
}
#p#分页标题#e#
留意,实现代码并未包括在这些例子中。这些代码并不重要,真正有代价的 是道理。您不该该体贴 WidgetDAOImpl 的 getOrderStatus() 要领是如何运作 的。它可以从数据库可能从某个文件系统中得到状态信息,但重点是这不会对您 发生什么影响!
此刻,疏散 GUI
因为 GUI 此刻依赖于某个抽象而且通过一个工场来得到该抽象的实现,所以 我们可以等闲地建设一个没有与数据库可能文件系统相耦合的仿照类。仿照类用 于疏散 GUI,如清单 8 所示:
清单 8. 疏散变得简朴
public class MockWidgetDAOImpl implements WidgetDAO {
public String getOrderStatus(String value) {
//..
}
}
添加一个仿照类是设计可维护性的系统的最后一个步调。把 GUI 与 数据库 分分开来意味着我们可以测试 GUI 而无需体贴特定的数据。我们还可以测试数 据会见逻辑而无需体贴 GUI。
竣事语
您大概没有过多地思量这些,可是您如今所设计和构建的应用措施利用寿命 大概很是持久。您将继承开拓其它的项目,可能在其它的公司事情,可是您的代 码(如 COBOL)将会留下来,甚至有大概利用几十年。
开拓人员所附和的一点是:编写精采的代码易于维护,依赖性倒置原则是进 行可维护性设计的靠得住要领。依赖性倒置注重依赖于抽象(而非实现),这样可 以在同一个代码库中建设大量的机动性。借助一个 DAO 来应用这个能力,就如 您这个月所看到的,不只可以确保您可以或许在需要的时候修改代码库,还可以使其 它的开拓人员修改代码库。