[英]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
,則必須考慮在對象的所有可能狀態下調用它(在對象加載之前,運行時,已經加載之后)。 簡而言之,要盡量少暴露
因此,要解決您的特定問題:
read
需要通過客戶端類確實是一個設計的味道 read
並熱切地預先加載所有內容。 決定是否延遲加載是一個取決於您上下文的實現細節,對您的類的客戶端來說無關緊要 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
的實例是不可變的。
read()
,然后根據需要多次調用getOutput()
異常。 您的更改將迫使用戶在以前不需要的上下文中捕獲已檢查的異常。 read()
是調用getOutput()
的前提,因此,類的責任是在用戶“忘記”創建用戶時“捕獲”用戶調用read()
。 IOException
,這可能是要捕獲的合法異常。 無法讓用戶知道是否要拋出異常,這在設計運行時異常時是一種不好的做法。 問題的根本原因是該類具有兩個正交職責:
如果將這兩個職責彼此分開,最終將得到一個更簡潔的設計,使用戶不會對他們必須調用的內容以及調用的順序感到困惑:
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.