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.pswp.cn/news/454854.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/454854.shtml
英文地址,請注明出處:http://en.pswp.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…