从一些小例子体会面向抽象编程(一)

来源:互联网 发布:日特绕线机编程 编辑:程序博客网 时间:2024/06/12 22:41
 

软件设计中最重要的一条原则就是“开-闭”原则(Open for extension, Closed for modification)。符合“开-闭”原则的系统,通常在稳定性、可扩展性、可插拔性等方面有非常良好的表现。实现“开-闭”原则,抽象化是关键,良好封装“可变”、“易变”部分是主要手段。我们要深刻体会面向抽象编程的本质和方法。

“高内聚、低耦合”是另一个大的设计原则。

最近从若干源码中看到几个小例子,摘录在这儿。

1. Log4j中的FileWatchdog

Log4j的源码,看到FileWatchdog相关的一部分代码。这个工具类可以自己启动一个线程,循环检查某一文件内容是否被修改,如果被修改,则触发相应的业务逻辑。

从功能上看,启动线程,定期循环检查文件内容是否发生变化这两部分基本是固定的、不变的。但是发现修改之后要触发的业务动作是可变、易变的。所以把后者封装成一个抽象的方法,在子类中延迟实现。与此同时,父类中的主流程面向抽象方法进行搭建。

该类是一个抽象类,需要根据具体的业务逻辑扩展自己的子类,实现具体的doOnChange()方法。

1.1 Class Diagram

1.2 FileWatchdog的相关方法

1.2.1 run()

While循环,每Sleep一段时间,检查被观察文件是否被修改。

while(!interrupted){    sleep(delay);    checkAndConfigure();}

1.2.2 checkAndConfigure()

检查文件是否被修改,如果被修改,则调用doOnChange()方法。

fileExists = file.exists();if (fileExists) {  if(file.lastModified()>lastModif){    doOnChange();  }  else{    log.warn(file doesn’t exit);  }}

1.2.3 doOnChange()

这是一个抽象方法,具体的操作应有具体子类实现。

1.3 FileWatchdog的使用

首先如第1.1.1节所示,扩展一个具体的子类,实现doOnChange()方法。比如Log4j中,有一个XMLWatchdog类:

public class XMLWatchdog extends FileWatchdog {    public XMLWatchdog(String filename) {    super(filename);  }  public void doOnChange() {    new DOMConfigurator().doConfigure(filename,  LogManager.getLoggerRepository());  }}


其次,对XMLWatchdog的调用,new出对象实例,设置属性,然后调用start方法。如下所示:

DOMConfigurator.configureAndWatch(fileName, delay){  XMLWatchdog xdog = new XMLWatchdog(filename);  xdog.setDelay(delay);  xdog.start();}


2. JDK中的FilenameFilter

在JDK的File类中,有一个list(FilenameFilter filter) 方法,实现的功能是根据filter定义的规则,获取并过滤该File对象对应的文件夹下面符合规则的文件和子文件夹的名字。

这里FilenameFilter是一个小接口:

interface FilenameFilter{  boolean accept(File dir, String name);}


 

接口中accept方法定义了过滤的规则。

从设计的角度看,Filename的过滤功能和File本身没有太紧密的关系,过滤规则本身也是比较独立的、可变、易变的。所以JDK一方面把这部分设计成抽象的(因为可变、易变),另一方面把它设计成一个小接口(与File类关系不大,比较独立)。同时list方法定义FilenameFilter接口作为参数,采用的是面向抽象的、参数注入方法。

2.1 Class Structure

2.2 File.list()方法

pulbic Strring[] list(FilenameFilter filter) {        String names[] = list();if ((names == null) || (filter == null)) {    return names;}ArrayList v = new ArrayList();for (int i = 0 ; i < names.length ; i++) {    if (filter.accept(this, names[i])) {v.add(names[i]);    }}return (String[])(v.toArray(new String[0]));    }

3. Spring中的RowMapper

在Spring Framework源码中,通过JdbcTemplate.query(String sql, RowMapper rowMapper)方法,对SQL的执行结果resultset进行处理,返回相应的List结果。本质上RowMapper的职责是把resultset变成用户期望的ValueObject对象List。

3.1 Class Diagram

3.2 Call Work Flow

JdbcTemplate.query(String sql, RowMapper mapper)方法注入RowMapper接口。执行逻辑为:

  • 注入RowMapper参数,封装出一个ResultSetExtractor实例
  • 调用ResultSetExtractor.extractData(…)方法
  • ResultSetExtractor.extractData(…)方法循环处理resultSet集合,对每一个结果行,调用注入的RowMapper对象的mapRow(…)方法。最终返回一个List对象
  • JdbcTemplate返回一个Object对象(这里本质上是一个List对象)。

由上可见,JdbcTemplate.query()方法,执行SQL语句,最终结果经ResultSetExtractor接口,调用用户传入的RowMapper实例进行格式转换,返回List对象。

注意,ResultSetExtractor接口有多个实现子类,如SqlRowSetResultSetExtractor,RowMapperResultSetExtractor,RowCallbackHandlerResultSetExtractor等。主要是extractData(ResultSet)方法实现逻辑不同。

对于RowMapperResultSetExtractor,需要通过构造函数注入一个RowMapper实例,负责实际每行结果集的map工作。

整个过程体现了面向对象编程、封装“可变易变部分”的思路。用户只需扩展出自己的RowMapper实例即可。因为所有Exception处理都在JdbcTemplate中有所处理,所以用户在自己的RowMapper实现中不需要关心Exception处理。

4. 小结

通过学习优秀的开源代码,逐渐体会并掌握系统设计的一些基本原则,应该是一个很好的“临摹”过程,对自己的设计水平会有比较大的提高。这个方法应该是一个快速入门的捷径。

 

 

 

原创粉丝点击