溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Java中代碼重用的示例分析

發(fā)布時間:2021-07-10 11:00:57 來源:億速云 閱讀:144 作者:小新 欄目:編程語言

這篇文章主要為大家展示了“Java中代碼重用的示例分析”,內(nèi)容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“Java中代碼重用的示例分析”這篇文章吧。

我?guī)缀醪恍枰懻摓槭裁粗赜么a是有利的。代碼重用通常使得程序開發(fā)更加快速,并使得 BUG 減少。一旦一段代碼被封裝和重用,那么只需要檢查很少的一段代碼即可確保程序的正確性。如果在整個應(yīng)用程序中只需要在一個地方打開和關(guān)閉數(shù)據(jù)庫連接,那么確保連接是否正常則容易的多。但我確信這些你已經(jīng)都知道了。

有兩種類型的重用代碼,我稱它們?yōu)橹赜妙愋停?/p>

  • 功能重用(Action Reuse)

  • 上下文重用(Context Reuse)

第一種類型是功能重用,這是最常見的一種重用類型。這也是大多數(shù)開發(fā)人員掌握的一種。即重用一組后續(xù)指令來執(zhí)行某種操作。

第二種類型是上下文重用,即不同功能或操作代碼在相同上下文之間,將相同上下文封裝為重用代碼(這里的上下文指的是一系列相同的操作指令)。雖然它在控制反轉(zhuǎn)中越來越受歡迎但它并不常見。而且,上下文重用并沒有被明確的描述,因此它并沒有像功能重用一樣被系統(tǒng)的使用。我希望你看完這篇文章之后會有所改變。

功能重用

功能重用是最常見的重用類型。它是一組執(zhí)行某種操作指令的重用。下面兩個方法都是從數(shù)據(jù)庫中讀取數(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
  }
}

對于有經(jīng)驗的開發(fā)人員來說,可能很快就能發(fā)現(xiàn)可以重用的代碼。上面代碼中注釋“重用代碼”的地方是相同的,因此可以封裝重用。這些是將用戶記錄讀入用戶實例的操作。可以將這些行代碼封裝到他們自己的方法中,例如:

// 將相同操作封裝到 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()方法(下面示例只顯示第一個方法):

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í)行特定操作的指令通過方法或類封裝它們來達(dá)到重用的目的。

參數(shù)化操作

有時,你希望重用一組操作,但是這些操作在使用的任何地方都不完全相同。例如readAllUsers()和readUsersOfStatus()方法都是打開一個連接,準(zhǔn)備一條語句,執(zhí)行它,并循環(huán)訪問結(jié)果集。唯一的區(qū)別是readUsersOfStatus()需要在PreparedStatement上設(shè)置一個參數(shù)。我們可以將所有操作封裝到一個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});
}

我相信你可以找出其他更好的辦法來實現(xiàn)重用功能,并將他們參數(shù)化使得更加好用。

上下文重用

上下文重用與功能重用略有不同。上下文重用是一系列指令的重用,各種不同的操作總是在這些指令之間進(jìn)行。換句話說,重復(fù)使用各種不同行為之前和之后的語句。因此上下文重用通常會導(dǎo)致控制風(fēng)格類的反轉(zhuǎn)。上下文重用是重用異常處理,連接和事務(wù)生命周期管理,流迭代和關(guān)閉以及許多其他常見操作上下文的非常有效的方法。

這里有兩個方法都是用 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)閉流。一次又一次的編寫這樣的錯誤處理和資源釋放代碼是很繁瑣且容易出錯的。錯誤處理和正確的連接處理在 JDBC 事務(wù)中更加復(fù)雜。編寫一次代碼并在任何地方重復(fù)使用顯然會比較容易。

幸運的是,封裝上下文的方法很簡單。 創(chuàng)建一個上下文類,并將公共上下文放入其中。 在上下文的使用中,將不同的操作指令抽象到操作接口之中,然后將每個操作封裝在實現(xiàn)該操作接口的類中(這里稱之為操作類),只需要將該操作類的實例插入到上下文中即可??梢酝ㄟ^將操作類的實例作為參數(shù)傳遞給上下文對象的構(gòu)造函數(shù),或者通過將操作類的實例作為參數(shù)傳遞給上下文的具體執(zhí)行方法來完成。

下面展示了如何將上述示例分隔為上下文和操作接口。StreamProcessor(操作接口)作為參數(shù)傳遞給StreamProcessorContext的processStream()方法。

// 流處理插件接口
public interface StreamProcessor {
  public void process(int input);
}
// 流處理上下文類
public class StreamProcessorContext{
  // 將 StreamProcessor 操作接口實例化并作為參數(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");
// 通過實現(xiàn) StreamProcessor 接口的匿名子類傳遞操作實例
new StreamProcessorContext()
.processStream(inputStream, new StreamProcessor(){
  public void process(int input){
    System.out.print((char) input);
  }
});

或者像下面這樣讀取輸入流內(nèi)容并添加到一個字符序列中:

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接口實現(xiàn)來對流做任何操作。一旦StreamProcessorContext被完全實現(xiàn),你將永遠(yuǎn)不會有關(guān)于未關(guān)閉流的困擾。

上下文重用非常強大,可以在流處理之外的許多其他環(huán)境中使用。一個明顯的用例是正確處理數(shù)據(jù)庫連接和事務(wù)(open - process - commit()/rollback() - close())。其他用例是 NIO 通道處理和臨界區(qū)中的線程同步(lock() - access shared resource - unlock())。它也能將API的已檢查異常轉(zhuǎn)換為未檢查異常。

當(dāng)你在自己的項目中查找適合上下文重用的代碼時,請查找以下操作模式:

  • 常規(guī)操作之前(general action before)

  • 特殊操作(special action)

  • 常規(guī)操作之后(general action after)

當(dāng)你找到這樣的模式時,前后的常規(guī)操作就可能實現(xiàn)上下文重用。

上下文作為模板方法

有時候你會希望在上下文中有多個插件點。如果上下文由許多較小的步驟組成,并且你希望上下文的每個步驟都可以自定義,則可以將上下文實現(xiàn)為模板方法。模板方法是一種 GOF 設(shè)計模式?;旧希0宸椒▽⑺惴ɑ騾f(xié)議分成一系列步驟。一個模板方法通常作為一個單一的基類實現(xiàn),并為算法或協(xié)議中的每一步提供一個方法。要自定義任何步驟,只需創(chuàng)建一個擴展模板方法基類的類,并重寫要自定義的步驟的方法。

下面的示例是作為模板方法實現(xiàn)的 JdbcContext。子類可以重寫連接的打開和關(guān)閉, 以提供自定義行為。必須始終重寫processRecord(ResultSet result)方法, 因為它是抽象的。此方法提供不屬于上下文的操作,在使用JdbcContext的不同情況下的操作都不相同。這個例子不是一個完美的JdbcContext。它僅用于演示在實現(xiàn)上下文時如何使用模板方法。

public abstract class JdbcContext {
  DataSource dataSource = null;
  // 無參數(shù)的構(gòu)造函數(shù)可以用于子類不需要 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示例中,操作代碼封裝在單獨的對象中,并作為方法參數(shù)提供。實現(xiàn)操作接口StreamProcessor的對象作為參數(shù)傳遞給StreamContext類的processStream(...)方法。

實施上下文時,你可以使用這兩種技術(shù)。JdbcContext類可以將實現(xiàn)操作接口的ConnectionOpener和ConnectionCloser對象作為參數(shù)傳遞給execute方法,或作為構(gòu)造函數(shù)的參數(shù)。就我個人而言,我更喜歡使用單獨的操作對象和操作接口,原因有兩個。首先,它使得操作代碼可以更容易單獨進(jìn)行單元測試;其次,它使得操作代碼在多個上下文中可重用。當(dāng)然,操作代碼也可以在代碼中的多個位置使用,但這只是一個優(yōu)勢。畢竟,在這里我們只是試圖重用上下文,而不是重用操作。

結(jié)束語

現(xiàn)在你已經(jīng)看到了兩種不同的重用代碼的方法。經(jīng)典的功能重用和不太常見的上下文重用。希望上下文的重用會像功能重用一樣普遍。上下文重用是一種非常有用的方法,可以從 API 的底層細(xì)節(jié)(例如JDBC,IO 或 NIO API等)中抽象出代碼。特別是如果 API 包含需要管理的資源(打開和關(guān)閉,獲得并返回等)。

persistence/ORM API、Mr.Persister 利用上下文重用來實現(xiàn)自動連接和事務(wù)生命周期管理。 這樣用戶將永遠(yuǎn)不必?fù)?dān)心正確打開或關(guān)閉連接,或提交或回滾事務(wù)。Mr.Persister 提供了用戶可以將他們的操作插入的上下文。 這些上下文負(fù)責(zé)打開,關(guān)閉,提交和回滾。

流行的 Spring 框架包含大量的上下文重用。 例如 Springs JDBC 抽象。 Spring 開發(fā)人員將其使用上下文重用作為“控制反轉(zhuǎn)”。 這不是 Spring 框架使用的唯一一種控制反轉(zhuǎn)類型。 Spring 的核心特性是依賴注入 bean 工廠或“應(yīng)用程序上下文”。 依賴注入是另一種控制反轉(zhuǎn)。

以上是“Java中代碼重用的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI