我们知道紧密耦合的代码不是个好现象,因此要在设计中尽量避免它 —— 但问题是如何才能避免紧密耦合呢。这个月,我们将学习如何识别一个系统是否有紧密耦合的问题,然后使用依赖性倒置原则解开这种紧密耦合。
虽然代码度量和开发人员测试对于在整个开发过程中确保代码质量非常重要(就像我经常所说的,要及时并经常进行测试),但是它们基本上只能对代码质量做出反应。您通过测试和度量代码来确定和量化代码的质量,但是代码本身都已经写好了。不论您做出何等努力,都会受困于最初的设计。
| 改进代码质量 不要错过 Andrew 的 代码质量讨论论坛,该论坛可以帮助您解决代码度量和测试框架方面的问题,以及如何编写以质量为中心的代码。 | |
当然,不同的方法所设计出来的软件系统会有好有坏,良莠不齐。优秀设计的关键因素之一就是注意保持系统的可维护性。粗劣设计的并可执行的系统可能易于编写,但是要对它们提供支持确实是一个挑战。这些系统往往脆弱不堪,也就是说对系统中某个区域的修改将会影响到其它看上去毫不相干的区域,因此要对它们进行重构也相当的困难和耗时。向代码库中添加开发人员测试可以为我们提供工作的规划,但是其进展本身仍然是一个艰苦和缓慢的过程。
我们可以通过重构来改进已经编写好的代码,但是通常来说在代码已完成之后再进行改动花费巨大。而如果在一开始就把代码编写得 尽善尽美 会不会更加方便和轻松呢? 这个月,我将介绍一种非常主动的技巧,可以确保软件系统的质量和可维护性。依赖性倒置原则 被证明是编写可维护和可测试的高质量代码的必要条件。依赖性倒置原则的基本思想就是对象应该依赖于抽象 而不是实现。
| 是依赖性倒置 而不是依赖性注入 依赖性倒置原则与依赖性注入并没有直接的关系。依赖性注入,也被称作控制反转(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
});
单击 GUI 的按钮组件后,直接从数据库中检索某个特定命令的状态,如清单 2 所示:
清单 2. GUI 通过 getOrderStatus 方法直接与数据库通信
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) {
//...
}
}
注意,实现代码并未包含在这些例子中。这些代码并不重要,真正有价值的是原理。您不应该关心 WidgetDAOImpl
的 getOrderStatus()
方法是如何运作的。它可以从数据库或者从某个文件系统中获得状态信息,但重点是这不会对您产生什么影响!
现在,分离 GUI
因为 GUI 现在依赖于某个抽象并且通过一个工厂来获得该抽象的实现,所以我们可以轻易地创建一个没有与数据库或者文件系统相耦合的模仿类。模仿类用于分离 GUI,如清单 8 所示:
清单 8. 分离变得简单
public class MockWidgetDAOImpl implements WidgetDAO {
public String getOrderStatus(String value) {
//..
}
}
添加一个模仿类是设计可维护性的系统的最后一个步骤。把 GUI 与 数据库分离开来意味着我们可以测试 GUI 而无需关心特定的数据。我们还可以测试数据访问逻辑而无需关心 GUI。
结束语
您可能没有过多地考虑这些,但是您如今所设计和构建的应用程序使用寿命可能非常长久。您将继续开发其它的项目,或者在其它的公司工作,但是您的代码(如 COBOL)将会留下来,甚至有可能使用几十年。
开发人员所赞同的一点是:编写良好的代码易于维护,依赖性倒置原则是进行可维护性设计的可靠方法。依赖性倒置注重依赖于抽象(而非实现),这样可以在同一个代码库中创建大量的灵活性。借助一个 DAO 来应用这个技巧,就如您这个月所看到的,不仅可以确保您能够在需要的时候修改代码库,还可以使其它的开发人员修改代码库。
分享到:
相关推荐
投资东南亚:谨防潜在税务风险.docx
大类资产配置7月报·战术篇:谨防降息预期反转,美股黄金再次预警-0716-中信建投-13页.pdf
标题和描述中提到的事件是一起因高抛光物体聚焦阳光引发的火灾事故。这个案例涉及到光学原理和消防安全知识,具体来说,以下是一些相关的知识点: 1. **聚焦原理**:高抛光物体如不锈钢盆在阳光下可以起到类似放大...
谨防! 游戏可能会导致用户畏缩。灵感 Being UCI students, we want to give our fellow students the platform to have a good laugh while also testing their agility skills.它能做什么 The game cons
2月油脂油料市场展望:油脂谨防大幅回调,粕或随美豆偏强运行.pdf
谨防! (我希望在错误解决后立即删除此警告) 刻面尺度 facetscales的目标是让您在每个图上使用具有不同比例的facet_grid 。 例如,这对于以不同的面以不同的单位显示时很有用。 该软件包的最终目的是在正确的...
文件标题指明了文档是关于“法务学习”,特别聚焦于“签合同要谨防8种合同陷阱”。因此,我们可以围绕这个话题,展开相关的知识点讲解。 知识点一:合同的基本概念 合同是具有法律效力的文件,用于明确双方或多方...
谨防“养老床位”变相为“医疗床位” 本文主题是讨论“医养结合”的概念和实践,特别是在养老床位和医疗床位之间的界限问题。作者认为,医养结合应该是指把对失能老人的基本生活照料服务与医疗服务紧密衔接起来,而...
尽管宝宝树在母婴O2O市场取得了显著成绩,但行业竞争日益激烈,如“大姨吗”等竞品的存在,要求宝宝树必须不断创新和提升服务质量,以保持领先地位。同时,随着BAT(百度、阿里巴巴、腾讯)等巨头进入母婴市场,如何...
谨防上当受骗!基本功能如下:查看留言:后台现在可以设置前台留言分页显示条数添加留言:添加留言加入了验证码,防止被灌水,同时也加入防简单的XSS攻击管理留言:就是进入后台进行留言管理后台路径:/system/index...
谨防上当受骗! XYCMS留言板基本功能 查看留言:后台现在可以设置前台留言分页显示条数 添加留言:添加留言加入了验证码,防止被灌水,同时也加入防简单的XSS攻击 管理留言:就是进入后台进行留言管理 注意事项...
报告主要围绕2019年3月的镍和不锈钢市场进行分析,提出“旺季预期谨防过度炒作,三月镍价高位震荡”的观点。以下是详细的知识点解析: 1. **精炼镍供应**:2019年全球精炼镍产能增加缓慢,企业将重心转向硫酸镍领域...
【知识点详解】 这篇初中语文语文论文探讨的是教育者在与学生交流中需要注意的语言表达方式,以避免挫伤学生的自尊心和积极性。以下是几个关键的知识点: 1. **避免指责性语言**: 教师应避免使用如“我早就跟你说...
员工福利系列模板-谨防年终奖发放的“雷区”.xls
全民反诈宣教视频8:谨防刷单诈骗mp4 全民反诈宣教视频9:剧单的悲惨日记.m4v 全民反诈宣教视频10:剧单——拒绝沉没成本,MP4 全民反诈宣教视频11:刷单(1).mp4 全民反诈宣教视频12:刷单(2).mp4
谨防!!!PSequel:Postgres的查询辅助器PSequel的目标是成为一个框架,该框架将使桥接nodejs代码和postgreSQL查询变得更加容易。 这样做将避免直接使用SQL查询来获取和更新数据,并提供将使用对象并对数据进行...
全民反诈宣教视频8:谨防刷单诈骗mp4 全民反诈宣教视频9:剧单的悲惨日记.m4v 全民反诈宣教视频10:剧单——拒绝沉没成本,MP4 全民反诈宣教视频11:刷单(1).mp4 全民反诈宣教视频12:刷单(2).mp4
全民反诈宣教视频8:谨防刷单诈骗mp4 全民反诈宣教视频9:剧单的悲惨日记.m4v 全民反诈宣教视频10:剧单——拒绝沉没成本,MP4 全民反诈宣教视频11:刷单(1).mp4 全民反诈宣教视频12:刷单(2).mp4
全民反诈宣教视频8:谨防刷单诈骗mp4 全民反诈宣教视频9:剧单的悲惨日记.m4v 全民反诈宣教视频10:剧单——拒绝沉没成本,MP4 全民反诈宣教视频11:刷单(1).mp4 全民反诈宣教视频12:刷单(2).mp4
全民反诈宣教视频8:谨防刷单诈骗mp4 全民反诈宣教视频9:剧单的悲惨日记.m4v 全民反诈宣教视频10:剧单——拒绝沉没成本,MP4 全民反诈宣教视频11:刷单(1).mp4 全民反诈宣教视频12:刷单(2).mp4