簡體   English   中英

設計供其他開發人員在Java中使用的類

[英]designing classes for other developers to use in java

class CSVReader  {

private List<String> output;
private InputStream input; 

public CSVReader(InputStream input) {
this.input = input;
}

public void read() throws Exception{
  //do something with the inputstream
  // create output list.
}

public List<String> getOutput() {
return Collections.unmodifiableList(output);
}

我正在嘗試創建一個簡單的類,該類將成為庫的一部分。 我想創建滿足以下條件的代碼:

  • 處理所有潛在錯誤或將它們包裝到庫錯誤中並拋出它們。
  • 創建有意義且完整的對象狀態(沒有不完整的對象結構)。
  • 開發人員易於使用該庫

現在,當我針對目標評估以上代碼時,我意識到我失敗了。 使用此代碼的開發人員必須編寫類似以下內容的代碼-

CSVReader reader = new CVSReader(new FileInputStream("test.csv");
reader.read();
read.getOutput();

我立即看到以下問題-開發人員必須先調用read,然后再獲取getOutput。 他沒有辦法直覺地知道這一點,這可能是不好的設計。

因此,我決定修復代碼並編寫如下內容

public List<String> getOutput() throws IOException{
if(output==null) 
   read();
return Collections.unmodifiableList(output);
}

或這個

public List<String> getOutput() {
if(output==null) 
   throw new IncompleteStateException("invoke read before getoutput()");
return Collections.unmodifiableList(output);
}

或這個

public CSVReader(InputStream input) {
read(); //throw runtime exception
}

或這個

public List<String> read() throws IOException {
//read and create output list.
// return list
}

什么是實現目標的好方法? 對象狀態是否應該總是定義良好? -永遠不會有未定義“輸出”的狀態,因此我應該將輸出作為構造函數的一部分來創建嗎? 還是該類通過在發現“輸出”未定義並且僅拋出運行時異常時調用“讀取”來確保創建的實例始終有效? 這里有什么好的方法/最佳實踐?

我將read()設為私有,並讓getOutput()調用它作為實現細節。 如果暴露read()是延遲加載文件,則可以只暴露getOutput來做到這一點。

public List<String> getOutput() {
   if (output == null) { 
      try {
        output = read();
      } catch (IOException) {
        //here you either wrap into your own exception and then declare it in the signature of getOutput, or just not catch it and make getOutput `throws IOException`
      }
   }
   return Collections.unmodifiableList(output);
}

這樣做的好處是您的類的接口非常簡單:您給我一個輸入(通過構造函數),我給您一個輸出(通過getOutput),在保持延遲加載的同時沒有魔術的調用順序,如果該文件很不錯大。

從公共API中刪除read另一個好處是,您可以從延遲加載到快速加載,反之亦然,而不會影響客戶端。 如果公開read ,則必須考慮在對象的所有可能狀態下調用它(在對象加載之前,運行時,已經加載之后)。 簡而言之,要盡量少暴露

因此,要解決您的特定問題:

  1. 是的,對象狀態應始終定義良好。 你不知道,在外部調用的點read需要通過客戶端類確實是一個設計的味道
  2. 是的,您可以在構造函數中調用read並熱切地預先加載所有內容。 決定是否延遲加載是一個取決於您上下文的實現細節,對您的類的客戶端來說無關緊要
  3. 如果未調用read則引發異常,這又增加了按正確的隱式順序在客戶端上調用事物的負擔,這是不必要的,因為您評論說輸出永遠不會真正不確定,因此實現本身可以做出無風險的決定何時致電read

我建議您使類盡可能的小,將getOutput()方法放在一起。

這個想法是讓一個類讀取CSV文件並返回一個表示結果的列表。 為此,您可以公開一個read()方法,該方法將返回List<String>

就像是:

public class CSVReader {

    private final InputStream input;

    public CSVReader(String filename) {
        this.input = new FileInputStream(filename);
    }

    public List<String> read() {
        // perform the actual reading here
    }
}

您有一個定義明確的類,一個較小的接口可以維護,並且CSVReader的實例是不可變的。

  1. 第一種方法從API中獲得了一定的靈活性:在進行更改之前,用戶可以在預期有異常的上下文中調用read() ,然后根據需要多次調用getOutput()異常。 您的更改將迫使用戶在以前不需要的上下文中捕獲已檢查的異常。
  2. 第二種方法是首先應該完成的方法:由於調用read()是調用getOutput()的前提,因此,類的責任是在用戶“忘記”創建用戶時“捕獲”用戶調用read()
  3. 第三種方法隱藏IOException ,這可能是要捕獲的合法異常。 無法讓用戶知道是否要拋出異常,這在設計運行時異常時是一種不好的做法。

問題的根本原因是該類具有兩個正交職責:

  • 讀取CSV,然后
  • 存儲讀取結果以供以后使用。

如果將這兩個職責彼此分開,最終將得到一個更簡潔的設計,使用戶不會對他們必須調用的內容以及調用的順序感到困惑:

interface CSVData {
    List<String> getOutput();
}
class CSVReader {
    public static CSVData read(InputStream input) throws IOException {
        ...
    }
}

您可以使用工廠方法將兩者合並為一個類:

class CSVData {
    private CSVData() { // No user instantiation
    }
    // Getting data is exception-free
    public List<String> getOutput() {
        ...
    }
    // Creating instances requires a factory call
    public static CSVData read(InputStream input) throws IOException {
        ...
    }
}

getOutput檢查它是否為null(或已過期),如果是,則自動將其加載。 這使您的班級用戶不必關心班級文件管理的內部狀態。

但是,您可能還希望公開read功能,以便用戶可以在方便時選擇加載文件。 如果您為並發環境創建該類,則建議這樣做。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM