Java code reuse: reuse functions and context

I hardly need to discuss why it is advantageous to reuse code. Code reuse usually make application development faster, and makes BUG reduction. Once a code is packaged and reused, then only a small piece of code can be checked to ensure program correctness. If you only need to open and close the database connection in one place throughout the application, then it is connected properly to ensure that much easier. But I'm sure those you already know.

There are two types of reusable code, I call them to reuse type:

  • Function reuse (Action Reuse)
  • Reuse context (Context Reuse)

The first type is a function of reuse, which is the most common form of reuse type. This is also the personnel in a most developers. I.e., a reuse set of subsequent instructions to perform certain operations.

The second type of context is reused, i.e., different functions or the same context between the operation code in the same package as the code reuse context (context herein refers to a series of the same operation instruction). Although it is increasingly popular in the inversion of control but it is not common. Moreover, the reuse of context and are not explicitly described, so it does not reuse the same function as the use of the system. I hope that will change after you read this article.

Reuse function

Function reuse is the most common type of reuse. It is a set of instructions to perform an operation to reuse. The following two methods are to read data from the database:

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
    }
}

For experienced developers, it may soon be able to find reusable code. The above comments code "reuse code" are the same place, so the package can be reused. These records are read into the user's instance of the user operation. These lines can be encapsulated into their own way, for example:

// 将相同操作封装到 readUser 方法中
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;  
}

Now, the two methods described above call readUser()process (following example shows only the first method):

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()The method can also be used modifier in its own class privatehidden.

These are the details about the function reuse. Function is to reuse a set of instructions to perform a particular operation or type of packaging by a method to achieve the purpose of reuse thereof.

Parameterization

Sometimes, you want to reuse a set of actions, but these actions are not exactly the same as anywhere use. For example, readAllUsers()and readUsersOfStatus()the method is to open a connection, prepare a statement, execute it, and iterate through the result set. The only difference is the readUsersOfStatus()need PreparedStatementto set a parameter on. We can operate all encapsulated in a readUserList()method. As follows:

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
    }
}

Now we are from readAllUsers()and readUsersOfStatus()invoke readUserList(...)methods, and given the different operating parameters:

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

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

I'm sure you can find other better ways to reuse capabilities, and parametric makes them easier to use.

Reuse context

Context reuse and reuse function is slightly different. Reuse context reuse is a series of instructions, the various operations are always performed between these instructions. In other words, repeated statements using a variety of different behaviors before and after. Thus usually leads to reusing the context class inversion control style. Reuse is the reuse context exception handling, connectivity, and lifecycle management, and flow iteration closed, and many other common operational context of a very effective method affairs.

There are two methods are used InputStream do:

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;
        }
    }
}

Both methods of operation flow is different. However, the context surrounding these operations are the same. Iterative context code and close the InputStream. In addition to the above code is different from the user comment is its context code.

As indicated above, context related to exception handling, and to ensure proper flow off after iteration. Again and again to write such error handling and resource release code is very tedious and error-prone. Error handling and proper connection process is more complicated in the JDBC transaction. Write the code once and reuse it clear that it will be easier anywhere.

Fortunately, the package is simple context. Creating a class context, public context and put it. In the context of the various operational instructions into the abstract operation interface, and then the operation of each encapsulated in the implementation class of the operation of the interface (referred to herein as type operation), just insert the example of the operation of the class to the context. Examples can be accomplished by the operation of the class as an argument to the constructor context object, or passed as a parameter to a specific method of performing the context by way of example the operation of the class.

The following example shows how the above context and the partition of operator interface. StreamProcessor(Operator interface) as a parameter to StreamProcessorContextthe processStream()method.

// 流处理插件接口
public interface StreamProcessor {
    public void process(int input);
}
// 流处理上下文类
public class StreamProcessorContext{
    // 将 StreamProcessor 操作接口实例化并作为参数
    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;
            }
        }
    }
}

As the following example can now be used as StreamProcessorContextkind of printing out the streaming content:

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

Reading the input stream or the like and added to the contents of a character following sequence:

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();

As you can see, by inserting different StreamProcessorto convection interface to do anything. Once StreamProcessorContextfully realized, you will never be bothered about the stream is not closed.

Context reuse is very powerful, it can be many other environments outside the stream processing in use. A clear and correctly handle the transaction database connection with embodiment (open - process - commit () / rollback () - close ()). Another use case is NIO critical zone and channel processing thread synchronization (lock () - access shared resource - unlock ()). It is also possible to checked exceptions into unchecked exceptions API.

When you find a suitable context to reuse code in your own projects, look for the following modes of operation:

  • Before normal operation (general action before)
  • Special operation (special action)
  • After normal operation (general action after)

When you find such a pattern before and after the normal operation may implement context reuse.

Context as a template method

Sometimes you will want to have more plug points in context. If the context of a number of smaller steps, and you want the context of each step can be customized, it can be implemented as a template context method. Template Method design pattern is a GOF. Basically, the method of the template algorithm or protocol into a series of steps. A template method generally as a single base class implementation, and to provide a method for each step of the algorithm or protocol. To customize any of the steps, just create a class template extend the base class and override the method steps to self-definition.

The following example is a method implemented as a template JdbcContext. Subclasses can override the connection opening and closing to provide custom behavior. We must always override the processRecord(ResultSet result)method, because it is abstract. This method provides a context not operating, using JdbcContextthe operation under different circumstances are different. This example is not perfect JdbcContext. It is only used to demonstrate how to use the template method when implementing context.

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);
            }
        }
    }
}

This is an extension JdbcContext user to read a list of sub-categories:

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);
    }
}

Here's how to use ReadUsers categories:

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

If the ReadUsersclass needs to obtain the connection and after their use to release the back connection from the connection pool pool, you can override openConnection()and closeConnection(Connection connection)be inserted into the connection method.

Notice how rewrite operation code inserted by the process. JdbcContextSubclasses override processRecordmethod to provide a particular recording process. In StreamContext example, the operation code is encapsulated in a separate object, and to provide a method parameter. Implement user interface StreamProcessorobject is passed as a parameter to the StreamContextclass processStream(...)method.

The implementation of context, you can use both technologies. JdbcContextClass interface operation may be implemented ConnectionOpenerand the ConnectionCloserobject as an argument to the executemethod, or as an argument to the constructor. For me personally, I prefer to use a separate operation objects and user interface, for two reasons. First, it enables the operator code can more easily separate test unit; secondly, it makes the operation code reuse in multiple contexts. Of course, the operation code may be used in a plurality of positions in the code, but this is an advantage. After all, here we are just trying to reuse context, rather than reusing operation.

Conclusion

Now that you've seen two different ways to reuse code. Classic function reuse and less common context reuse. Context will want to reuse as common as the function reuse. Context reuse is a very useful method, the code can be abstracted from the underlying details of the API (e.g. JDBC, IO the NIO API, or the like). Especially if the API contains resources need to be managed (open and closed, access and return, etc.).

persistence / ORM API, Mr.Persister weight using a context used for automatic connection and transaction lifecycle management. So users will never have to worry about the right to open or close the connection, or commit or roll back the transaction. Mr.Persister provides users can insert their operational context. The context is responsible for opening, closing, commit and rollback.

Popular Spring Framework contains a lot of context reuse. For example Springs JDBC abstraction. Spring developers to reuse the context as "inversion of control." This is the only inversion type of control is not used in the Spring Framework. Spring core feature is dependent injection or bean plant "application context." Dependency injection is another control inversion.

Guess you like

Origin www.linuxidc.com/Linux/2019-06/158976.htm