Java代碼重用之功能與上下文重用
我?guī)缀醪恍枰懻摓槭裁粗赜么a是有利的。代碼重用通常使得程序開(kāi)發(fā)更加快速,并使得 BUG 減少。一旦一段代碼被封裝和重用,那么只需要檢查很少的一段代碼即可確保程序的正確性。如果在整個(gè)應(yīng)用程序中只需要在一個(gè)地方打開(kāi)和關(guān)閉數(shù)據(jù)庫(kù)連接,那么確保連接是否正常則容易的多。但我確信這些你已經(jīng)都知道了。
有兩種類型的重用代碼,我稱它們?yōu)橹赜妙愋停?/p>
- 功能重用(Action Reuse)
- 上下文重用(Context Reuse)
第一種類型是功能重用,這是最常見(jiàn)的一種重用類型。這也是大多數(shù)開(kāi)發(fā)人員掌握的一種。即重用一組后續(xù)指令來(lái)執(zhí)行某種操作。
第二種類型是上下文重用,即不同功能或操作代碼在相同上下文之間,將相同上下文封裝為重用代碼(這里的上下文指的是一系列相同的操作指令)。雖然它在控制反轉(zhuǎn)中越來(lái)越受歡迎但它并不常見(jiàn)。而且,上下文重用并沒(méi)有被明確的描述,因此它并沒(méi)有像功能重用一樣被系統(tǒng)的使用。我希望你看完這篇文章之后會(huì)有所改變。
功能重用
功能重用是最常見(jiàn)的重用類型。它是一組執(zhí)行某種操作指令的重用。下面兩個(gè)方法都是從數(shù)據(jù)庫(kù)中讀取數(shù)據(jù):
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 } }
對(duì)于有經(jīng)驗(yàn)的開(kāi)發(fā)人員來(lái)說(shuō),可能很快就能發(fā)現(xiàn)可以重用的代碼。上面代碼中注釋“重用代碼”的地方是相同的,因此可以封裝重用。這些是將用戶記錄讀入用戶實(shí)例的操作。可以將這些行代碼封裝到他們自己的方法中,例如:
// 將相同操作封裝到 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; }
現(xiàn)在,在上述兩種方法中調(diào)用readUser()方法(下面示例只顯示第一個(gè)方法):
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隱藏。
以上就是關(guān)于功能重用的內(nèi)容。功能重用是將一組執(zhí)行特定操作的指令通過(guò)方法或類封裝它們來(lái)達(dá)到重用的目的。
參數(shù)化操作
有時(shí),你希望重用一組操作,但是這些操作在使用的任何地方都不完全相同。例如readAllUsers()和readUsersOfStatus()方法都是打開(kāi)一個(gè)連接,準(zhǔn)備一條語(yǔ)句,執(zhí)行它,并循環(huán)訪問(wèn)結(jié)果集。唯一的區(qū)別是readUsersOfStatus()需要在PreparedStatement上設(shè)置一個(gè)參數(shù)。我們可以將所有操作封裝到一個(gè)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 } }
現(xiàn)在我們從readAllUsers()
和readUsersOfStatus()
調(diào)用readUserList(...)
方法,并給定不同的操作參數(shù):
public List readAllUsers(){ return readUserList("select * from users", new String[]{}); } public List readUsersWithStatus(String status){ return readUserList("select * from users", new String[]{status}); }
我相信你可以找出其他更好的辦法來(lái)實(shí)現(xiàn)重用功能,并將他們參數(shù)化使得更加好用。
上下文重用
上下文重用與功能重用略有不同。上下文重用是一系列指令的重用,各種不同的操作總是在這些指令之間進(jìn)行。換句話說(shuō),重復(fù)使用各種不同行為之前和之后的語(yǔ)句。因此上下文重用通常會(huì)導(dǎo)致控制風(fēng)格類的反轉(zhuǎn)。上下文重用是重用異常處理,連接和事務(wù)生命周期管理,流迭代和關(guān)閉以及許多其他常見(jiàn)操作上下文的非常有效的方法。
這里有兩個(gè)方法都是用 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; } } }
兩種方法與流的操作是不同的。但圍繞這些操作的上下文是相同的。上下文代碼迭代并關(guān)閉 InputStream。上述代碼中除了使用注釋標(biāo)記的不同之處外都是其上下文代碼。
如上所示,上下文涉及到異常處理,并保證在迭代后正確關(guān)閉流。一次又一次的編寫(xiě)這樣的錯(cuò)誤處理和資源釋放代碼是很繁瑣且容易出錯(cuò)的。錯(cuò)誤處理和正確的連接處理在 JDBC 事務(wù)中更加復(fù)雜。編寫(xiě)一次代碼并在任何地方重復(fù)使用顯然會(huì)比較容易。
幸運(yùn)的是,封裝上下文的方法很簡(jiǎn)單。 創(chuàng)建一個(gè)上下文類,并將公共上下文放入其中。 在上下文的使用中,將不同的操作指令抽象到操作接口之中,然后將每個(gè)操作封裝在實(shí)現(xiàn)該操作接口的類中(這里稱之為操作類),只需要將該操作類的實(shí)例插入到上下文中即可??梢酝ㄟ^(guò)將操作類的實(shí)例作為參數(shù)傳遞給上下文對(duì)象的構(gòu)造函數(shù),或者通過(guò)將操作類的實(shí)例作為參數(shù)傳遞給上下文的具體執(zhí)行方法來(lái)完成。
下面展示了如何將上述示例分隔為上下文和操作接口。StreamProcessor(操作接口)作為參數(shù)傳遞給StreamProcessorContext的processStream()方法。
// 流處理插件接口 public interface StreamProcessor { public void process(int input); } // 流處理上下文類 public class StreamProcessorContext{ // 將 StreamProcessor 操作接口實(shí)例化并作為參數(shù) 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; } } } }
現(xiàn)在可以像下面示例一樣使用StreamProcessorContext類打印出流內(nèi)容:
FileInputStream inputStream = new FileInputStream("myFile"); // 通過(guò)實(shí)現(xiàn) StreamProcessor 接口的匿名子類傳遞操作實(shí)例 new StreamProcessorContext() .processStream(inputStream, new StreamProcessor(){ public void process(int input){ System.out.print((char) input); } });
或者像下面這樣讀取輸入流內(nèi)容并添加到一個(gè)字符序列中:
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();
正如你所看到的,通過(guò)插入不同的StreamProcessor接口實(shí)現(xiàn)來(lái)對(duì)流做任何操作。一旦StreamProcessorContext被完全實(shí)現(xiàn),你將永遠(yuǎn)不會(huì)有關(guān)于未關(guān)閉流的困擾。
上下文重用非常強(qiáng)大,可以在流處理之外的許多其他環(huán)境中使用。一個(gè)明顯的用例是正確處理數(shù)據(jù)庫(kù)連接和事務(wù)(open - process - commit()/rollback() - close()
)。其他用例是 NIO 通道處理和臨界區(qū)中的線程同步(lock() - access shared resource - unlock()
)。它也能將API的已檢查異常轉(zhuǎn)換為未檢查異常。
當(dāng)你在自己的項(xiàng)目中查找適合上下文重用的代碼時(shí),請(qǐng)查找以下操作模式:
- 常規(guī)操作之前(general action before)
- 特殊操作(special action)
- 常規(guī)操作之后(general action after)
當(dāng)你找到這樣的模式時(shí),前后的常規(guī)操作就可能實(shí)現(xiàn)上下文重用。
上下文作為模板方法
有時(shí)候你會(huì)希望在上下文中有多個(gè)插件點(diǎn)。如果上下文由許多較小的步驟組成,并且你希望上下文的每個(gè)步驟都可以自定義,則可以將上下文實(shí)現(xiàn)為模板方法。模板方法是一種 GOF 設(shè)計(jì)模式?;旧希0宸椒▽⑺惴ɑ騾f(xié)議分成一系列步驟。一個(gè)模板方法通常作為一個(gè)單一的基類實(shí)現(xiàn),并為算法或協(xié)議中的每一步提供一個(gè)方法。要自定義任何步驟,只需創(chuàng)建一個(gè)擴(kuò)展模板方法基類的類,并重寫(xiě)要自定義的步驟的方法。
下面的示例是作為模板方法實(shí)現(xiàn)的 JdbcContext。子類可以重寫(xiě)連接的打開(kāi)和關(guān)閉, 以提供自定義行為。必須始終重寫(xiě)processRecord(ResultSet result)方法, 因?yàn)樗浅橄蟮?。此方法提供不屬于上下文的操作,在使用JdbcContext的不同情況下的操作都不相同。這個(gè)例子不是一個(gè)完美的JdbcContext。它僅用于演示在實(shí)現(xiàn)上下文時(shí)如何使用模板方法。
public abstract class JdbcContext { DataSource dataSource = null; // 無(wú)參數(shù)的構(gòu)造函數(shù)可以用于子類不需要 DataSource 來(lái)獲取連接 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(); } // 必須始終重寫(xiě) 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); } } } }
這是擴(kuò)展 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類需要從連接池獲取連接并在使用后將其釋放回該連接池,則可以通過(guò)重寫(xiě)openConnection()
和closeConnection(Connection connection)
方法來(lái)插入該連接。
注意如何通過(guò)方法重寫(xiě)插入操作代碼。JdbcContext的子類重寫(xiě)processRecord方法以提供特殊的記錄處理。 在StreamContext示例中,操作代碼封裝在單獨(dú)的對(duì)象中,并作為方法參數(shù)提供。實(shí)現(xiàn)操作接口StreamProcessor的對(duì)象作為參數(shù)傳遞給StreamContext類的processStream(...)
方法。
實(shí)施上下文時(shí),你可以使用這兩種技術(shù)。JdbcContext類可以將實(shí)現(xiàn)操作接口的ConnectionOpener和ConnectionCloser對(duì)象作為參數(shù)傳遞給execute方法,或作為構(gòu)造函數(shù)的參數(shù)。就我個(gè)人而言,我更喜歡使用單獨(dú)的操作對(duì)象和操作接口,原因有兩個(gè)。首先,它使得操作代碼可以更容易單獨(dú)進(jìn)行單元測(cè)試;其次,它使得操作代碼在多個(gè)上下文中可重用。當(dāng)然,操作代碼也可以在代碼中的多個(gè)位置使用,但這只是一個(gè)優(yōu)勢(shì)。畢竟,在這里我們只是試圖重用上下文,而不是重用操作。
結(jié)束語(yǔ)
現(xiàn)在你已經(jīng)看到了兩種不同的重用代碼的方法。經(jīng)典的功能重用和不太常見(jiàn)的上下文重用。希望上下文的重用會(huì)像功能重用一樣普遍。上下文重用是一種非常有用的方法,可以從 API 的底層細(xì)節(jié)(例如JDBC,IO 或 NIO API等)中抽象出代碼。特別是如果 API 包含需要管理的資源(打開(kāi)和關(guān)閉,獲得并返回等)。
persistence/ORM API、Mr.Persister 利用上下文重用來(lái)實(shí)現(xiàn)自動(dòng)連接和事務(wù)生命周期管理。 這樣用戶將永遠(yuǎn)不必?fù)?dān)心正確打開(kāi)或關(guān)閉連接,或提交或回滾事務(wù)。Mr.Persister 提供了用戶可以將他們的操作插入的上下文。 這些上下文負(fù)責(zé)打開(kāi),關(guān)閉,提交和回滾。
流行的 Spring 框架包含大量的上下文重用。 例如 Springs JDBC 抽象。 Spring 開(kāi)發(fā)人員將其使用上下文重用作為“控制反轉(zhuǎn)”。 這不是 Spring 框架使用的唯一一種控制反轉(zhuǎn)類型。 Spring 的核心特性是依賴注入 bean 工廠或“應(yīng)用程序上下文”。 依賴注入是另一種控制反轉(zhuǎn)。
以上所述是小編給大家介紹的Java代碼重用之功能與上下文重用,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
java實(shí)現(xiàn)學(xué)生成績(jī)信息管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)學(xué)生成績(jī)信息管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-07-07Java將一個(gè)正整數(shù)分解質(zhì)因數(shù)的代碼
這篇文章主要介紹了將一個(gè)正整數(shù)分解質(zhì)因數(shù)。例如:輸入90,打印出90=2*3*3*5,需要的朋友可以參考下2017-02-02springboot接受前端請(qǐng)求的方法實(shí)現(xiàn)
本文主要介紹了springboot接受前端請(qǐng)求的方法實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01MPAndroidChart開(kāi)源圖表庫(kù)的使用介紹之餅狀圖、折線圖和柱狀圖
這篇文章主要介紹了MPAndroidChart開(kāi)源圖表庫(kù)的使用介紹之餅狀圖、折線圖和柱狀圖的相關(guān)資料,需要的朋友可以參考下2016-02-02Spring Cloud Stream異常處理過(guò)程解析
這篇文章主要介紹了Spring Cloud Stream異常處理過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-08-08如何把VS Code打造成Java開(kāi)發(fā)IDE
這篇文章主要介紹了如何把VS Code打造成Java開(kāi)發(fā)IDE,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10SpringController返回值和異常自動(dòng)包裝的問(wèn)題小結(jié)
今天遇到一個(gè)需求,在不改動(dòng)原系統(tǒng)代碼的情況下,將Controller的返回值和異常包裝到一個(gè)統(tǒng)一的返回對(duì)象中去,下面通過(guò)本文給大家介紹SpringController返回值和異常自動(dòng)包裝的問(wèn)題,需要的朋友可以參考下2024-03-03