Java 代码重用:操作与上下文重用

目录

  • 功能重用
    • 参数化操作
  • 上下文重用
    • 上下文作为模板方法
  • 结束语

我几乎不需要讨论为什么重用代码是有利的。代码重用通常使得程序开发更加快速,并使得 BUG 减少。一旦一段代码被封装和重用,那么只需要检查很少的一段代码即可确保程序的正确性。如果在整个应用程序中只需要在一个地方打开和关闭数据库连接,那么确保连接是否正常则容易的多。但我确信这些你已经都知道了。

有两种类型的重用代码,我称它们为重用类型:

  • 功能重用(Action Reuse)
  • 上下文重用(Context Reuse)

第一种类型是功能重用,这是最常见的一种重用类型。这也是大多数开发人员掌握的一种。即重用一组后续指令来执行某种操作。

第二种类型是上下文重用,即不同功能或操作代码在相同上下文之间,将相同上下文封装为重用代码(这里的上下文指的是一系列相同的操作指令)。虽然它在控制反转中越来越受欢迎但它并不常见。而且,上下文重用并没有被明确的描述,因此它并没有像功能重用一样被系统的使用。我希望你看完这篇文章之后会有所改变。

功能重用

功能重用是最常见的重用类型。它是一组执行某种操作指令的重用。下面两个方法都是从数据库中读取数据:

public List readAllUsers(){
    Connection connection = null;
    String sql = "select * from users";
    List users = new ArrayList();
    try{
        connection  = openConnection();
        PreparedStatement statement = connection.prepareStatement(sql);
        ResultSet result = statement.executeQuery();
        while(result.next()){
            // 重用代码
            User user = new User();
            user.setName (result.getString("name"));
            user.setEmail(result.getString("email"));
            users.add(user);
            // END 重用代码
        }
        result.close();
        statement.close();
        return users;
    }
    catch(SQLException e){
        //ignore for now
    }
    finally {
        //ignore for now
    }
}
public List readUsersOfStatus(String status){
    Connection connection = null;
    String sql = "select * from users where status = ?";
    List users = new ArrayList();
    try{
        connection  = openConnection();
        PreparedStatement statement = connection.prepareStatement(sql);
        statement.setString(1, status);
        ResultSet result = statement.executeQuery();
        while(result.next()){
            // 重用代码
            User user = new User();
            user.setName (result.getString("name"));
            user.setEmail(result.getString("email"));
            users.add(user);
            // END 重用代码
        }
        result.close();
        statement.close();
        return users;
    }
    catch(SQLException e){
        //ignore for now
    }
    finally {
        //ignore for now
    }
}

对于有经验的开发人员来说,可能很快就能发现可以重用的代码。上面代码中注释标记的地方是相同的,因此可以封装重用。这些行是将用户记录读入用户实例的操作。可以将这些行代码封装到他们自己的方法中,例如:

private User readUser(ResultSet result) throws SQLException {
   User user = new User();
   user.setName (result.getString("name"));
   user.setEmail(result.getString("email"));
   users.add(user);
   return user;
}

现在,可以从上述两种方法中调用readUser()方法(示例显示第一个方法):

public List readAllUsers(){
    Connection connection = null;
    String sql = "select * from users";
    List users = new ArrayList();
    try{
        connection  = openConnection();
        PreparedStatement statement = connection.prepareStatement(sql);
        ResultSet result = statement.executeQuery();
        while(result.next()){
            users.add(readUser(result))
        }
        result.close();
        statement.close();
        return users;
    }
    catch(SQLException e){
        //ignore for now
    }
    finally {
        //ignore for now
    }
}

readUser()方法也可以在它自己的类中使用私有方法隐藏。

上面就是关于功能重用的内容。功能重用是将一组执行特定操作的指令通过方法或类封装它们来达到重用。

参数化操作

有时,你希望重用一组指令,但是这些指令在使用的任何地方都不完全相同。例如readAllUsers()readUsersOfStatus()方法都是打开一个连接,准备一条语句,执行它,并循环访问结果集。唯一的区别是readUsersOfStatus()PreparedStatement上设置一个参数。这两种方法可以合并为一个readUserList()方法,如下所示:

private List readUserList(String sql, String[] parameters){
    Connection connection = null;
    List users = new ArrayList();
    try{
        connection  = openConnection();
        PreparedStatement statement = connection.prepareStatement(sql);
        for (int i=0; i < parameters.length; i++){
            statement.setString(i, parameters[i]);
        }
        ResultSet result = statement.executeQuery();
        while(result.next()){
            users.add(readUser(result)) // 重用
        }
        result.close();
        statement.close();
        return users;
    }
    catch(SQLException e){
        //ignore for now
    }
    finally {
        //ignore for now
    }
}

现在可以像下面这样从readAllUsers()readUsersOfStatus()调用此方法:

public List readAllUsers(){
   return readUserList("select * from users", new String[]{});
}

public List readUsersWithStatus(String status){
   return readUserList("select * from users", new String[]{status});
}

我相信你可以找出其他更好的办法来实现重用操作,并将他们参数化更加可重用。

上下文重用

上下文重用与功能重用略有不同。上下文重用是一系列指令的重用,各种不同的操作总是在这些指令之间进行。换句话说,重复使用各种不同行为之前和之后的语句。因此上下文重用通常会导致控制风格类的反转。上下文重用是重用异常处理,连接和事务生命周期管理,流迭代和关闭以及许多其他常见操作上下文的非常有效的方法。

这里有两个方法都是用InputStream做的:

public void printStream(InputStream inputStream) throws IOException {
    if(inputStream == null) return;
    IOException exception = null;
    try{
        int character = inputStream.read();
        while(character != -1){
            System.out.print((char) character); // 不同
            character = inputStream.read();
        }
    }
    finally {
        try{
            inputStream.close();
        }
        catch (IOException e){
            if(exception == null) throw e;
        }
    }
}
public String readStream(InputStream inputStream) throws IOException {
    StringBuffer buffer = new StringBuffer(); // 不同
    if(inputStream == null) return;
    IOException exception = null;
    try{
        int character = inputStream.read();
        while(character != -1){
            buffer.append((char) character); // 不同
            character = inputStream.read();
        }
        return buffer.toString(); // 不同
    }
    finally {
        try{
            inputStream.close();
        }
        catch (IOException e){
            if(exception == null) throw e;
        }
    }
}

两种方法与流的操作是不同的。但围绕这些操作的上下文是相同的。上下文代码迭代并关闭 InputStream。上述代码中除了使用注释标记的不同之处外都是其上下文代码。

如上所示,上下文涉及到异常处理,并保证在迭代后正确关闭流。一次又一次的编写这样的错误处理和资源释放是很繁琐且容易出错的。错误处理和正确的连接处理在JDBC事务中更加复杂。编写一次并在任何地方重复使用会比较容易。

幸运的是,封装上下文的方法很简单。 上下文本身被放入它自己的类中。 在上下文的使用中,不同的操作被抽象到操作接口之中。 然后将每个操作封装在实现该操作接口的类中。上下文只需要插入一个操作实例。可以通过将操作实例作为参数传递给上下文对象构造函数,或者通过将操作实例作为参数传递给上下文的 execute 方法来完成。

下面展示了如何将上述示例分隔为上下文和操作。StreamProcessor(操作)作为参数传递给StreamProcessorContextprocessStream()方法。

// 流处理插件接口
public interface StreamProcessor {
    public void process(int input);
}
// 流处理上下文类
public class StreamProcessorContext{
    // 下面将 StreamProcessor processor 操作实例作为参数
    public void processStream(InputStream inputStream, StreamProcessor processor) throws IOException {
        if(inputStream == null) return;
        IOException exception = null;
        try{
            int character = inputStream.read();
            while(character != -1){
                processor.process(character);
                character = inputStream.read();
            }
        }
        finally {
            try{
                inputStream.close();
            }
            catch (IOException e){
                if(exception == null) throw e;
                throw exception;
            }
        }
    }
}

现在可以像下面示例一样使用StreamProcessorContext类打印出流内容:

FileInputStream inputStream = new FileInputStream("myFile");
// 通过实现 StreamProcessor 接口的匿名子类传递操作实例
new StreamProcessorContext()
.processStream(inputStream, new StreamProcessor(){
    public void process(int input){
        System.out.print((char) input);
    }
});

或者像下面这样读取输入流内容并添加到一个字符序列中:

public class StreamToStringReader implements StreamProcessor{
    private StringBuffer buffer = new StringBuffer();
    public StringBuffer getBuffer(){
        return this.buffer;
    }
    public void process(int input){
        this.buffer.append((char) input);
    }
}
FileInputStream inputStream = new FileInputStream("myFile");
StreamToStringReader reader = new StreamToStringReader();
new StreamProcessorContext().processStream(inputStream, reader);
// do something with input from stream.
reader.getBuffer();

正如你所看到的,通过插入不同的StreamProcessor接口实现来对流做任何操作。一旦StreamProcessorContext被完全实现,你将永远不会有关于未关闭流的困扰。

上下文重用非常强大,可以在流处理之外的许多其他环境中使用。 一个明显的用例是正确处理数据库连接和事务(open - process - commit()/rollback() - close())。 其他用例是 NIO 通道处理和临界区中的线程同步(lock() - access shared resource - unlock())。 它也能将API的已检查异常转换为未检查异常。

当你在自己的项目中查找适合上下文重用的代码时,请查找以下操作模式:

  • 常规操作之前(general action before)
  • 特殊操作(special action)
  • 常规操作之后(general action after)

当你找到这样的模式时,前后的常规操作就可能实现上下文重用。

上下文作为模板方法

有时候你会希望在上下文中有多个插件点。如果上下文由许多较小的步骤组成,并且你希望上下文的每个步骤都可以自定义,则可以将上下文实现为模板方法。模板方法是一种 GOF 设计模式。基本上,模板方法将算法或协议分成一系列步骤。一个模板方法通常作为一个单一的基类实现,并为算法或协议中的每一步提供一个方法。要自定义任何步骤,只需创建一个扩展模板方法基类的类,并重写要自定义的步骤的方法。

下面的示例是作为模板方法实现的 JdbcContext。子类可以重写连接的打开和关闭, 以提供自定义行为。必须始终重写processRecord(ResultSet result)方法, 因为它是抽象的。此方法提供不属于上下文的操作,在使用JdbcContext的不同情况下的操作都不相同。这个例子不是一个完美的JdbcContext。它仅用于演示在实现上下文时如何使用模板方法。

public abstract class JdbcContext {
    DataSource dataSource = null;
    // 无参数的构造函数可以用于子类不需要 DataSource 来获取连接
    public JdbcContext() {
    }
    public JdbcContext(DataSource dataSource){
        this.dataSource = dataSource;
    }
    protected Connection openConnection() throws SQLException{
        return dataSource.getConnection();
    }
    protected void closeConnection(Connection connection) throws SQLException{
        connection.close();
    }
    // 必须始终重写 processRecord(ResultSet result)  方法
    protected abstract processRecord(ResultSet result) throws SQLException ;
    public void execute(String sql, Object[] parameters) throws SQLException {
        Connection        connection = null;
        PreparedStatement statement  = null;
        ResultSet         result     = null;
        try{
            connection = openConnection();
            statement  = connection.prepareStatement(sql);
            for (int i=0; i < parameters.length; i++){
                statement.setObject(i, parameters[i]);
            }
            result = statement.executeQuery();
            while(result.next()){
                processRecord(result);
            }
        }
        finally {
            if(result     != null){
                try{
                    result.close();
                }
                catch(SQLException e) {
                    /* ignore */
                }
            }
            if(statement  != null){
                try{
                    statement.close();
                }
                catch(SQLException e) {
                    /* ignore */
                }
            }
            if(connection != null){
                closeConnection(connection);
            }
        }
    }
}

这是扩展 JdbcContext 以读取用户列表的子类:

public class ReadUsers extends JdbcContext{
    List users = new ArrayList();
    public ReadUsers(DataSource dataSource){
        super(dataSource);
    }
    public List getUsers() {
        return this.users;
    }
    protected void processRecord(ResultSet result){
        User user = new User();
        user.setName (result.getString("name"));
        user.setEmail(result.getString("email"));
        users.add(user);
    }
}

下面是如何使用 ReadUsers 类:

ReadUsers readUsers = new ReadUsers(dataSource);
readUsers.execute("select * from users", new Object[0]);
List users = readUsers.getUsers();

如果ReadUsers类需要从连接池获取连接并在使用后将其释放回该连接池,则可以通过重写openConnection()closeConnection(Connection connection)方法来插入该连接。

注意如何通过方法重写插入操作代码。JdbcContext的子类重写processRecord方法以提供特殊的记录处理。 在StreamContext示例中,操作代码封装在单独的对象中,并作为方法参数提供。实现操作接口StreamProcessor的对象作为参数传递给StreamContext类的processStream(...)方法。

实施上下文时,你可以使用这两种技术。JdbcContext类可以将实现操作接口的ConnectionOpenerConnectionCloser对象作为参数传递给execute方法,或作为构造函数的参数。就我个人而言,我更喜欢使用单独的操作对象和操作接口,原因有两个。首先,它使得操作代码可以更容易单独进行单元测试;其次,它使得操作代码在多个上下文中可重用。当然,操作代码也可以在代码中的多个位置使用,但这只是一个优势。毕竟,在这里我们只是试图重用上下文,而不是重用操作。

结束语

现在你已经看到了两种不同的重用代码的方法。经典的功能重用和不太常见的上下文重用。希望上下文的重用会像功能重用一样普遍。上下文重用是一种非常有用的方法,可以从 API 的底层细节(例如JDBC,IO 或 NIO API等)中抽象出代码。特别是如果 API 包含需要管理的资源(打开和关闭,获得并返回等)。

persistence/ORM API、Mr.Persister 利用上下文重用来实现自动连接和事务生命周期管理。 这样用户将永远不必担心正确打开或关闭连接,或提交或回滚事务。Mr.Persister 提供了用户可以将他们的操作插入的上下文。 这些上下文负责打开,关闭,提交和回滚。

流行的 Spring 框架包含大量的上下文重用。 例如 Springs JDBC 抽象。 Spring 开发人员将其使用上下文重用作为“控制反转”。 这不是 Spring 框架使用的唯一一种控制反转类型。 Spring 的核心特性是依赖注入 bean 工厂或“应用程序上下文”。 依赖注入是另一种控制反转。

参考文章:http://tutorials.jenkov.com/ood/code-reuse-action-and-context-reuse.html#closing-notes

原文地址:https://www.cnblogs.com/nwgdk/p/9008548.html

时间: 2024-10-18 20:12:19

Java 代码重用:操作与上下文重用的相关文章

浅谈使用java代码进行操作Excel文件

使用java代码操作Excel文件 需要用到的依赖 <!--poi--><dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>4.1.0</version></dependency><dependency> <groupId>org.apache.poi<

通过java代码实现调用excel当中的宏的操作。

最近做的项目是自动化生成基金的交易合同的操作.然后就想着通过java代码去操作Excel当中的宏按钮,然后生成word版本的合同的操作. 具体的java代码如下: import org.junit.Assert; import org.junit.Test; public class ExcelToolsServiceTest { private ExcelToolsService service; @Test public void testMacro() { try { String pat

Zookeeper客户端java代码操作

Zookeeper客户端java代码操作 上篇博客记录了shell命令操作zookeeper集群的方式,这次尝试采用java代码来操作.通过查阅API,发现并不困难. 1. 首先获得客户端与服务器的连接 //zookeeper客户端 private ZooKeeper zkCli; //连接地址 private static final String CONNECT_STRING = "hadoop102:2181,hadoop103:2181,hadoop104:2181"; //s

python调用Java代码,完毕JBPM工作流application

1.缘由 有一庞大Python django webproject,要引入工作流引擎,像OA一样.方便的流程控制与管理.Python或django关于工作流的开源插件,稀少,并且弱爆了,终于选用java的开源框架JBPM.为了使Pythonproject整合Java框架,尝试过jbpm-console提供的 REST, 以失败告终,终于选用Python 直接调用Java代码.操作JBPM. 1.1pythonprojectREST与JBPM Workbench交互 失败 REST URL格式 h

java目录相关操作 示例代码

package org.rui.io; import java.io.File; import java.io.FilenameFilter; import java.util.Arrays; import java.util.regex.Pattern; /** * 目录列表器 测试 * * @author lenovo * */ //Args:"C:/Users/lenovo/Pictures/screen/*\.jpg" public class DirList { public

不使用spring的情况下原生java代码两种方式操作mongodb数据库

由于更改了mongodb3.0数据库的密码,导致这几天storm组对数据进行处理的时候,一直在报mongodb数据库连接不上的异常.   主要原因实际上是和mongodb本身无关的,因为他们改的是配置文件的密码,而实际上这个密码在代码中根本就没有使用,他们在代码中已经把用户验证信息写死.   在协助他们解决这个问题的时候,我看到他们代码中在和mongodb数据库交互时使用了已经不被建议使用的方法,于是便抽时间尝试了一下另一种被建议的方式实现各功能.   当然了,生产环境中用的是mongodb集群

HDFS的文件操作(Java代码实现)

对于hdfs的操作,可以使用hadoop fs 的命令,同时也可以使用java来进行操作,下面的一段小例子,就是简单介绍了java操作hdfs的文件等...... package com.hdfs.nefu; /** * @auther XD **/ import java.io.FileInputStream; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import

jsp页面:js方法里嵌套java代码(是操作数据库的),如果这个js 方法没被调用,当jsp页面被解析的时候,不管这个js方法有没有被调用这段java代码都会被执行?

jsp页面:js方法里嵌套java代码(是操作数据库的),如果这个js 方法没被调用,当jsp页面被解析的时候,不管这个js方法有没有被调用这段java代码都会被执行? 因为在解析时最新解析的就是JAVA代码,不管写哪里,都会最新被解析

Java代码操作zookeeper

以下为一个完整JAVA操作Zookeeper项目步骤: 1. 项目中pom.xml中添加需要的依赖jar包信息 <dependencies> <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.4.9</version> </dependency> <