[英]Best practice for passing many arguments to method?
有時,我們必須編寫接收許多 arguments 的方法,例如:
public void doSomething(Object objA , Object objectB ,Date date1 ,Date date2 ,String str1 ,String str2 )
{
}
遇到這種問題,我經常把arguments封裝成一個map。
Map<Object,Object> params = new HashMap<Object,Object>();
params.put("objA",ObjA) ;
......
public void doSomething(Map<Object,Object> params)
{
// extracting params
Object objA = (Object)params.get("objA");
......
}
這不是一個好的做法,將 params 封裝成 map 完全是浪費效率。 好處是,干凈的簽名,只需最少的修改即可輕松添加其他參數。 此類問題的最佳做法是什么?
在Effective Java ,第 7 章(方法),第 40 項(仔細設計方法簽名)中,Bloch 寫道:
有三種縮短過長參數列表的技術:
更多細節,我鼓勵你買這本書,真的很值得。
使用帶有魔法字符串鍵的地圖是一個壞主意。 您失去了任何編譯時檢查,而且還不清楚所需的參數是什么。 您需要編寫非常完整的文檔來彌補它。 幾周后,您會在不看代碼的情況下記住這些字符串是什么嗎? 如果你打錯了怎么辦? 使用錯誤的類型? 直到你運行代碼你才會發現。
而是使用模型。 創建一個類,作為所有這些參數的容器。 這樣你就可以保持 Java 的類型安全。 您還可以將該對象傳遞給其他方法,將其放入集合等。
當然,如果這組參數沒有在其他地方使用或傳遞,則專用模型可能會過大。 有一個平衡需要達到,所以使用常識。
如果您有許多可選參數,您可以創建流暢的 API:用方法鏈替換單個方法
exportWithParams().datesBetween(date1,date2)
.format("xml")
.columns("id","name","phone")
.table("angry_robots")
.invoke();
使用靜態導入,您可以創建內部流暢的 API:
... .datesBetween(from(date1).to(date2)) ...
它被稱為“引入參數對象”。 如果您發現自己在多個地方傳遞了相同的參數列表,只需創建一個包含它們的類即可。
XXXParameter param = new XXXParameter(objA, objB, date1, date2, str1, str2);
// ...
doSomething(param);
即使您沒有發現自己經常傳遞相同的參數列表,這種簡單的重構仍然會提高您的代碼可讀性,這總是好的。 如果您在 3 個月后查看您的代碼,就會更容易理解何時需要修復錯誤或添加功能。
當然,這是一個普遍的哲學,因為你沒有提供任何細節,我也不能給你更詳細的建議。 :-)
首先,我會嘗試重構該方法。 如果它使用那么多參數,它可能太長了。 分解它既可以改進代碼,又可以減少每個方法的參數數量。 您也可以將整個操作重構為它自己的類。 其次,我會尋找使用相同參數列表的相同(或超集)的其他實例。 如果您有多個實例,則可能表明這些屬性屬於一起。 在這種情況下,創建一個類來保存參數並使用它。 最后,我會評估參數的數量是否值得創建地圖對象以提高代碼可讀性。 我認為這是個人的決定——這個解決方案的每一種方式都有痛苦,權衡點可能會有所不同。 對於六個參數,我可能不會這樣做。 對於 10,我可能會(如果其他方法都不起作用)。
這在構造對象時通常是一個問題。
在這種情況下,使用builder object pattern ,如果您有大量參數列表並且並不總是需要所有參數,它會很好地工作。
您還可以將其調整為方法調用。
它還大大增加了可讀性。
public class BigObject
{
// public getters
// private setters
public static class Buider
{
private A f1;
private B f2;
private C f3;
private D f4;
private E f5;
public Buider setField1(A f1) { this.f1 = f1; return this; }
public Buider setField2(B f2) { this.f2 = f2; return this; }
public Buider setField3(C f3) { this.f3 = f3; return this; }
public Buider setField4(D f4) { this.f4 = f4; return this; }
public Buider setField5(E f5) { this.f5 = f5; return this; }
public BigObject build()
{
BigObject result = new BigObject();
result.setField1(f1);
result.setField2(f2);
result.setField3(f3);
result.setField4(f4);
result.setField5(f5);
return result;
}
}
}
// Usage:
BigObject boo = new BigObject.Builder()
.setField1(/* whatever */)
.setField2(/* whatever */)
.setField3(/* whatever */)
.setField4(/* whatever */)
.setField5(/* whatever */)
.build();
您還可以將驗證邏輯放入 Builder set..() 和 build() 方法中。
有一種稱為Parameter object的模式。
想法是使用一個對象代替所有參數。 現在即使以后需要添加參數,也只需要將其添加到對象中即可。 方法接口保持不變。
您可以創建一個類來保存該數據。 雖然需要足夠有意義,但比使用地圖(OMG)要好得多。
Code Complete* 提出了幾點建議:
* 第一版,我知道我應該更新。 此外,自 OOP 開始變得越來越流行時編寫第二版以來,這些建議中的一些可能已經改變。
使用 Map 是一種清理調用簽名的簡單方法,但是您還有另一個問題。 您需要查看方法的主體內部以查看該方法在該 Map 中期望什么,鍵名稱是什么或值具有什么類型。
一種更簡潔的方法是將對象 bean 中的所有參數分組,但這仍然不能完全解決問題。
你在這里遇到的是一個設計問題。 如果一個方法有超過 7 個參數,您將開始難以記住它們代表什么以及它們的順序。 從這里開始,僅通過以錯誤的參數順序調用方法,您就會得到很多錯誤。
您需要更好的應用程序設計,而不是發送大量參數的最佳實踐。
好的做法是重構。 這些對象意味着它們應該傳遞給這個方法嗎? 是否應該將它們封裝到單個對象中?
創建一個 bean 類,並設置所有參數(setter 方法)並將此 bean 對象傳遞給該方法。
查看您的代碼,看看為什么要傳入所有這些參數。有時可以重構方法本身。
使用地圖會使您的方法容易受到攻擊。 如果有人在使用您的方法時拼錯了參數名稱,或者在您的方法需要 UDT 的地方發布了一個字符串怎么辦?
定義一個傳輸對象。 它至少會為您提供類型檢查; 您甚至可以在使用點而不是在您的方法內執行一些驗證。
我會說堅持你以前做的方式。 您示例中的參數數量並不多,但替代方案要可怕得多。
地圖 - 你提到的效率問題,但這里更大的問題是:
包裝對象 - 這只是解決了問題,因為您首先需要填充包裝對象 - 而不是直接填充到您的方法,而是填充到參數對象的構造函數。 確定移動問題是否合適取決於該對象的重用。 例如:
不會使用它:它只會在第一次調用時使用一次,所以很多額外的代碼來處理 1 行......?
{
AnObject h = obj.callMyMethod(a, b, c, d, e, f, g);
SomeObject i = obj2.callAnotherMethod(a, b, c, h);
FinalResult j = obj3.callAFinalMethod(c, e, f, h, i);
}
可以使用它:在這里,它可以做得更多。 首先,它可以分解 3 個方法調用的參數。 它本身也可以執行另外兩行……所以它在某種意義上變成了一個狀態變量……
{
AnObject h = obj.callMyMethod(a, b, c, d, e, f, g);
e = h.resultOfSomeTransformation();
SomeObject i = obj2.callAnotherMethod(a, b, c, d, e, f, g);
f = i.somethingElse();
FinalResult j = obj3.callAFinalMethod(a, b, c, d, e, f, g, h, i);
}
人們忘記考慮的另一件事是 IDE 在這一切中的作用。 當方法有參數時,IDE 會為您生成大部分代碼,您會看到紅線提醒您需要提供/設置什么。 使用選項 3 時……您完全失去了它。 現在由程序員來做正確的事情,並且在編碼和編譯期間沒有任何提示……程序員必須對其進行測試以找出答案。
此外,如果選項 2 和 3 被不必要地廣泛采用,由於它生成的大量重復代碼,在維護方面會產生長期的負面影響。 代碼越多,需要維護的越多,維護它所花費的時間和金錢就越多。
如果您傳遞的參數過多,請嘗試重構該方法。 也許它正在做很多它不應該做的事情。 如果不是這種情況,請嘗試用單個類替換參數。 通過這種方式,您可以將所有內容封裝在單個類實例中並傳遞實例而不是參數。
... 鮑勃是你的叔叔:用於 object 創建的無障礙花哨 API!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.