java 代码重用_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

}

}

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

// 将相同操作封装到 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;

}

现在,在上述两种方法中调用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()方法也可以在它自己的类中使用修饰符private隐藏。

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

参数化操作

有时,你希望重用一组操作,但是这些操作在使用的任何地方都不完全相同。例如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()调用readUserList(...)方法,并给定不同的操作参数:

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 事务中更加复杂。编写一次代码并在任何地方重复使用显然会比较容易。

幸运的是,封装上下文的方法很简单。 创建一个上下文类,并将公共上下文放入其中。 在上下文的使用中,将不同的操作指令抽象到操作接口之中,然后将每个操作封装在实现该操作接口的类中(这里称之为操作类),只需要将该操作类的实例插入到上下文中即可。可以通过将操作类的实例作为参数传递给上下文对象的构造函数,或者通过将操作类的实例作为参数传递给上下文的具体执行方法来完成。

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

// 流处理插件接口

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;

}

}

}

}

现在可以像下面示例一样使用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类可以将实现操作接口的ConnectionOpener和ConnectionCloser对象作为参数传递给execute方法,或作为构造函数的参数。就我个人而言,我更喜欢使用单独的操作对象和操作接口,原因有两个。首先,它使得操作代码可以更容易单独进行单元测试;其次,它使得操作代码在多个上下文中可重用。当然,操作代码也可以在代码中的多个位置使用,但这只是一个优势。毕竟,在这里我们只是试图重用上下文,而不是重用操作。

结束语

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/454854.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

GCC-3.4.6源代码学习笔记

大约4年前&#xff0c;我加入了GDNT - 北电网络在中国的合资企业&#xff0c;参与3G UMTS无线接入网的研发工作。与GCC有了第一次亲密的接触&#xff08;之前使用的是MS的VC&#xff09;。彼时&#xff0c;北电在其诸如&#xff0c;UMTS、CDMA、及自行开发的众多工具等项目中&a…

互联网

2019独角兽企业重金招聘Python工程师标准>>> 转载于:https://my.oschina.net/u/3127489/blog/1550726

GCC笔记 命令行分析

1984年&#xff0c;Richard Stallman发起了自由软件运动&#xff0c;GNU (Gnus Not Unix)项目应运而生&#xff0c;3年后&#xff0c;最初版的GCC横空出世&#xff0c;成为第一款可移植、可优化、支持ANSI C的开源C编译器。GCC最初的全名是GNU C Compiler,之后&#xff0c;随着…

java 反射用法_Java 反射的概念与使用

一&#xff0c;反射的概念对于一个人来说&#xff0c;了解自己的能力、本事、特点&#xff0c;对于他去干事创业来说&#xff0c;是很重要的。同样的&#xff0c;对于一门面向对象的语言来说&#xff0c;了解类(对象其实就是类的实现)本身也是重要的&#xff0c;可以在很多地方…

关于Unity中的Mesh Collider碰撞器

原来我的场景中有一个平面Plane带Mesh Collider碰撞器组件&#xff0c;一个主角Hero带有一个Box Collider碰撞器和有重力的Rigidbody刚体组件&#xff0c;主角可以放在平面上。 在导入场景后&#xff0c;隐藏平面Plane&#xff0c;给一个地板添加一个Mesh Collider碰撞器&#…

GCC常用选项使用详解

通常所说的GCC是GUN Compiler Collection的简称&#xff0c;除了编译程序之外&#xff0c;它还含其他相关工具&#xff0c;所以它能把易于人类使用的高级语言编写的源代码构建成计算机能够直接执行的二进制代码。GCC是Linux平台下最常用的编译程序&#xff0c;它是Linux平台编译…

java 井字棋 人机_井字游戏 人机对战 java实现

package com.ecnu.Main;/*** 主函数触发游戏*/public class MainApplication {public static void main(String[] args){TicTacToeGame ticTacToeGame new TicTacToeGame();ticTacToeGame.start();}}//TicTacToeGame 方法类import java.util.Scanner;public class TicTacToeGa…

Session(数据)共享的前后端分离Shiro实战

1&#xff0c;前言本文期望描述如何使用Shiro构建基本的安全登录和权限验证。本文实战场景有如下特殊需求&#xff1a;1&#xff0c;在集群和分布式环境实现session共享&#xff1b;2&#xff0c;前端只使用HTML/CSS/JS。因此无法直接使用Shiro提供的SessionManager&#xff0c…

读书笔记(javascript 高级程序设计)

一. 数据类型&#xff1a; 1. undefined&#xff1a; 未声明和未初始化的变量&#xff0c;typeof 操作符返回的结果都是 undefined&#xff1b;&#xff08;建议未初始化的变量进行显式赋值&#xff0c;这样当 typeof 返回 undefined 时就知道是未声明了&#xff0c;帮助定位问…

关于gcc扩展中的宏定义中用 # 和 ##

关于gcc扩展中的宏定义中用 "#" 和 "##"今天测试了宏定义中的 "#" 和 "##" 的区别。 结果如下&#xff1a; "#" 代表和一个字符串相连接 "##" 代表和一个符号连接&#xff0c;符号可以是变量&#xff0c;或另一…

java 年计算_java实现计算某年某月的天数

在计算某年某月的天数时&#xff0c;需要注意平年闰年。分析&#xff1a;闰年具体的判定方法就要看它的判定条件&#xff1a;四年一闰 &#xff0c; 百年不闰 &#xff0c;400年再闰。而计算该年该月的天数&#xff0c;又分大月和小月&#xff0c;特殊月份2月之分。(视频教程推…

添加自定义菜单,报错40155

2019独角兽企业重金招聘Python工程师标准>>> 提交的json中&#xff0c;某个自定义菜单对应的URL访问是有问题的&#xff0c;请挨个检查一下。 转载于:https://my.oschina.net/selly1025/blog/1551496

gcc编译流程及中间表示层RTL的探索

gcc编译流程及中间表示层RTL的探索收藏新一篇: 解读VC编程中的文件操作API和CFile类 | 旧一篇: Effective Item21 尽可能使用const 内容摘要 本文将以 C 语言为例&#xff0c;介绍 gcc 在接受一个 .c文件的输入之后&#xff0c;其前端是如何进行处理并得到一个中间表示并转交给…

【bzoj2132】圈地计划 网络流最小割

题目描述 最近房地产商GDOI(Group of Dumbbells Or Idiots)从NOI(Nuts Old Idiots)手中得到了一块开发土地。据了解&#xff0c;这块土地是一块矩形的区域&#xff0c;可以纵横划分为NM块小区域。GDOI要求将这些区域分为商业区和工业区来开发。根据不同的地形环境&#xff0c;每…

python爬虫爬取数据如何将br去掉_Python怎么去除爬取下来的网站中的一些转义字符串 - 收获啦...

基本方法其实用python爬取网页很简单&#xff0c;只有简单的几句话这样就可以获得到页面的内容。接下来再用正则匹配去匹配所需要的内容就行了。但是&#xff0c;真正要做起来&#xff0c;就会有各种各样的细节问题。2.登录这是一个需要登录认证的网站。也不太难&#xff0c;只…

Linux基础

Linux的特点&#xff1a; 系统版本&#xff1a;常见的有debian、Redhat更适合做服务器&#xff0c;更安全和稳定&#xff0c;Ubuntu唯一的优势就是图形界面好&#xff0c;centos目前被redhat收购&#xff0c;红旗已经倒闭。 1、免费的/开源的&#xff1b;2、支持多线程/多用户&…

GCC的编译和调试--入门介绍

编译与调试1.1编译的概念和理解在进行C程序开发时&#xff0c;编译就是将编写的C语言代码变成可执行程序的过程&#xff0c;这一过程是由编译器来完成的。编译器就是完成程序编译工作的软件&#xff0c;在进行程序编译时完成了一系列复杂的过程。1.1.1程序编译的过程在执行这一…

A* a=new B ,会不会产生内存泄露了,露了B-A的部分?

A* anew B ,delete a;会不会产生内存泄露了&#xff0c;露了B-A的部分。其中B为A的子类 析构函数在下边3种情况时被调用&#xff1a;1.对象生命周期结束&#xff0c;被销毁时&#xff1b;2.delete指向对象的指针时&#xff0c;或delete指向对象的基类类型指针&#xff0c;而其基…

spring 第一天:1015

对象加强的三种方法&#xff1a;1/继承2/装饰着模式3/动态调用 2&#xff1a;装饰着模式&#xff1a;就是就是1-先建一个基类 &#xff0c;如咖啡类 。味道很苦2- 再建一个类配料类 也就是说是所欲配料种类的父类。然后写多配料子类个子类继承配料类&#xff0c;。3-子类三个步…

java public 继承_java继承问题

代码&#xff1a;父类&#xff1a;public class Father {public Father() {System.out.println("基类构造函数{");show();new a();System.out.println("}");}public void show() {System.out.println("基类----show");}public class a {public a…