当心那对紧张的夫妻!

本文探讨了依赖倒置原则,一种确保软件系统质量和可维护性的主动技术。通过将对象依赖于抽象而非实现,可以提高代码的灵活性和可测试性。文章详细介绍了依赖倒置与依赖注入的区别,以及如何通过数据访问对象(DAO)模式实现松耦合,从而简化测试和维护。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在撰写本专栏的过去一年左右的时间里,我介绍了许多可用来提高代码质量的工具和技术。 我已经向您展示了如何应用代码指标来监视代码库的属性。 如何使用TestNG,FIT和Selenium等测试框架来验证应用程序功能; 以及如何使用XMLUnit和StrutsTestCase之类的扩展框架(以及Cargo和DbUnit之类的强大帮助器)来扩展测试框架的覆盖范围。

尽管代码指标和开发人员测试对于确保整个开发过程中的代码质量至关重要(就像我经常说的那样,要尽早且经常进行测试!),但它们本质上是React性技术。 您可以测试和测量代码以确定和量化其质量,但是代码本身已经被编写。 无论您做什么,都将陷于原始设计中。

当然,设计软件系统的方法越来越好。 良好设计的关键之一是关注可维护性。 设计和实施不佳的系统可能很容易编写,但要支持它们确实是一个挑战。 它们往往具有危险的脆弱性-意味着对系统某个区域的更改将影响其他看似无关的区域-因此重构起来既困难又耗时。 将开发人员测试添加到代码库中可以为您提供一张可以使用的地图,但是进度本身仍然十分缓慢。

您可以通过重构来改进已经编写的代码,但是在事实发生后引入更改通常很昂贵。 如果代码从一开始就编写得好 ,会不会更容易? 本月,我将介绍一种主动技术,以确保您的软件系统的质量和可维护性。 事实证明, 依赖反转原则对于编写高质量,可维护且可测试的代码至关重要。 依赖倒置背后的基本思想是对象应该依赖抽象而不是实现 。

太紧的夫妻

您可能至少听说过在面向对象编程的上下文中使用的术语“ 耦合” 。 耦合是指应用程序中组件(或对象)的相互关系。 松耦合的应用程序比紧密耦合的应用程序更加模块化。 它的组件依赖于接口和抽象类,而不是具体的组件,就像它们在紧密耦合的系统中一样。 在松散耦合的系统中,使用抽象而不是实现来使组件相互关联。

在图表中看到紧密耦合的问题很容易把握。 例如,看一下图1,它代表了一个GUI与其数据库耦合的软件系统:

图1.紧密耦合的系统
紧密耦合的系统

GUI对实现而不是抽象的依赖限制了系统。 如果不启动并运行数据库,则无法操作GUI。 从功能的角度来看,这种设计似乎还不错-我们多年来一直在编写这样的应用程序,毕竟飞机并没有从天空掉下来-但是测试是另外一回事了。

你说“脆”吗?

图1中的系统使得很难隔离编程问题,这对于测试和维护系统的各个方面都是至关重要的。 您将需要一个实时数据库,其中要适当地植入查找数据以测试GUI,并需要一个正常运行的GUI来测试数据访问逻辑。 您可以使用TestNG-Abbot(现已重命名为FEST)测试前端,但是仍然不能告诉您有关数据库功能的任何信息。

您可以在下面的清单1中看到这种有害的耦合。GUI的特定按钮定义了一个ActionListener ,它通过getOrderStatus调用直接与基础数据库进行通信。

清单1.为GUI中的按钮定义了一个ActionListener
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语句与硬编码的数据库直接通信。 e! 您是否可以想象开发人员测试此GUI和关联的数据库(顺便说一下,它们可能很容易成为Web页)的挑战? 考虑到对数据库的任何更改都会影响GUI,因此当您考虑实际修改系统时,情况会变得更糟。

让我放松!

现在,让我们考虑在设计时就考虑了依赖倒置原则的同一系统。 如图2所示,可以通过向应用程序添加两个组件来解耦该应用程序:一个是接口,另一个是实现:

图2.松耦合的系统
松耦合系统

在图2所示的应用程序中,GUI依赖于抽象-数据访问对象或DAO。 DAO的实现直接取决于数据库,但是GUI本身并不纠结。 以DAO形式添加抽象将数据库实现与GUI实现解耦。 现在代替数据库,将接口耦合到GUI代码。 该接口如清单3所示:

清单3. WidgetDAO是有助于解耦架构的抽象
public interface WidgetDAO {
  public String getOrderStatus(String widget);
  //....
}

GUI的ActionListener代码引用了清单3中定义的接口类型WidgetDAO ,而不是该接口的实际实现。 在清单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) {	
  //...
 }
}

请注意,这些示例未包含实现代码。 该代码并不重要-这才是最重要的原则。 您不必在意WidgetDAOImplgetOrderStatus()方法如何工作。 它可以从数据库或文件系统中获取状态-关键是,这对您来说无关紧要!

现在,隔离GUI

因为GUI现在依赖于抽象并从工厂获得了该抽象的实现,所以您可以轻松地创建一个不与数据库或文件系统耦合的模拟类。 该模拟隔离了GUI,如清单8所示:

清单8.隔离变得容易
public class MockWidgetDAOImpl implements WidgetDAO {
 public String getOrderStatus(String value) {		
   //..
 }
}

添加模拟是设计此系统以实现可维护性的最后一步。 将GUI与数据库隔离,反之亦然,这意味着您可以测试GUI而不用担心特定数据。 您也可以测试数据访问逻辑而无需担心GUI。

结论

您可能不会考虑太多,但是今天正在设计和构建的应用程序可能会比您的寿命更长久。 您将继续进行其他项目和公司,但是您的代码(例如COBOL)将滞后,甚至可能持续数十年。

开发人员已经达成共识的一件事是,编写良好的代码是可维护的,而依赖倒置原则是一种设计可维护性的可靠方法。 依赖关系反转强调了对实现的依赖,这在代码库内产生了很大的灵活性。 如本月您所见,在DAO的帮助下应用此技术不仅可以确保您能够在需要时修改代码库,而且其他开发人员也可以。


翻译自: https://www.ibm.com/developerworks/java/library/j-cq05227/index.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值