2010-03-12 155 views
69

有時,我們必須編寫收到很多很多爭論的方法,例如:將許多參數傳遞給方法的最佳實踐?

public void doSomething(Object objA , Object objectB ,Date date1 ,Date date2 ,String str1 ,String str2) 
{ 
} 

當我遇到這樣的問題,我常常參數封裝成圖。

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封裝到地圖中完全是浪費效率。 好的是,乾淨的簽名,易於添加其他參數,修改最少。 這類問題的最佳做法是什麼?

回答

101

Effective Java,第7章(方法),第40項(設計方法仔細簽名),布洛赫寫道:

有三種技術來縮短過長的參數列表:

  • 突破該方法分爲多個方法,每個方法只需要參數的一個子集
  • 創建幫助類來保存參數組(通常是靜態成員類)
  • 修改Builder對象構造模式方法調用。

欲瞭解更多詳情,我鼓勵您購買這本書,這是非常值得的。

+0

什麼是「過長的參數」?我們何時可以說方法有太多參數?是否有特定的數字或範圍? – 2017-01-14 22:39:39

+0

@RedM我一直認爲超過3或4個參數是「過長」 – jtate 2017-06-22 13:34:07

+1

@jtate是個人選擇還是你正在關注官方文檔? – 2017-06-22 14:57:34

2

好的做法是重構。這些對象意味着它們應該被傳遞給這個方法?它們是否應該封裝成一個單一的對象?

+0

是的,他們應該。例如,一個大型的搜索表單,有許多不相關的約束和需要分頁。你需要傳遞currentPageNumber,searchCriteria,pageSize ... – Sawyer 2010-03-12 12:00:07

4

您可以創建一個類來保存該數據。雖然需要足夠有意義,但要比使用地圖(OMG)好得多。

+0

我不認爲有必要創建一個類來保存方法參數。 – Sawyer 2010-03-12 11:56:58

+0

如果有多個傳遞相同參數的實例,我只會創建這個類。這將表明參數是相關的,可能無論如何都屬於這些參數。如果你正在爲一種方法創建一個類,治療可能比疾病更糟糕。 – tvanfosson 2010-03-12 12:00:50

+0

是的 - 您可以將相關的參數移動到DTO或值對象中。是否有多個參數可選,即主方法是否被這些附加參數重載?在這種情況下 - 我覺得這是可以接受的。 – JoseK 2010-03-12 12:01:45

61

使用神奇字符串鍵地圖是一個壞主意。你會失去任何編譯時間檢查,並且確實不清楚所需的參數是什麼。你需要編寫非常完整的文檔來彌補它。你會記得幾個星期之內那些字符串沒有看代碼嗎?如果你犯了一個錯字呢?使用錯誤的類型?直到您運行代碼時纔會發現。

改爲使用模型。創建一個類,它將成爲所有這些參數的容器。這樣你就保持了Java的類型安全。您也可以圍繞傳遞對象等方法,把它放在收藏等

當然,如果設定的參數是不是在其他地方使用或通過周圍,有專門的模型可能是矯枉過正。有一個平衡點,所以使用常識。

2

使用地圖是清潔調用簽名一個簡單的方法,但那麼你有另外一個問題。您需要查看方法的主體,以查看該方法在該Map中所期望的內容,關鍵名稱或值的類型。

一個清潔的方式將是組中的對象豆的所有參數,但仍不能完全解決問題。

你在這裏是一個設計問題。對於一個方法,如果有超過7個參數,你就會開始有問題記住它們代表的是什麼以及它們的順序。從這裏你將通過錯誤的參數順序調用該方法來獲得大量的錯誤。

您需要的應​​用程序不是一個最佳實踐的一個更好的設計來發送大量的參數。

5

有一種稱爲Parameter object模式。

想法是使用一個對象代替所有參數。現在,即使您稍後需要添加參數,您只需將其添加到對象即可。方法界面保持不變。

10

首先,我會嘗試重構該方法。如果它使用那麼多參數,它可能會太長。分解它既可以改善代碼,也可以減少每種方法的參數數量。您也許可以將整個操作重構爲自己的類。其次,我會查找其他情況下使用相同參數列表的相同(或超集)。如果您有多個實例,則可能表示這些屬性屬於一個整體。在這種情況下,創建一個類來保存參數並使用它。最後,我會評估參數的數量是否值得創建一個地圖對象來提高代碼的可讀性。我認爲這是個人呼籲 - 這種解決方案的每一個方面都很痛苦,而且權衡點可能有所不同。對於六個參數我可能不會這樣做。對於10我可能會(如果沒有其他方法首先工作)。

12

它被稱爲「引入參數對象」。如果你發現自己在幾個地方傳遞了相同的參數列表,只需創建一個包含它們的類。

XXXParameter param = new XXXParameter(objA, objB, date1, date2, str1, str2); 
// ... 
doSomething(param); 

即使你沒有發現自己傳遞相同的參數列表中,這樣的時候,那麼容易重構仍然會提高你的代碼的可讀性,這是總是好。如果您在3個月後查看自己的代碼,那麼當您需要修復錯誤或添加功能時,將更容易理解。

這是一個普遍的哲學當然,既然你沒有提供任何細節,我也不能給你更詳細的建議。 :-)

+0

垃圾回收會成爲問題嗎? – rupinderjeet 2016-12-07 04:00:04

+0

如果您在調用方函數中使參數對象爲本地作用域,並且您沒有對其進行變異,則不適用。在這種情況下,它很可能會被收集起來並且很快就會重用內存。 – dimitarvp 2016-12-07 04:05:33

+0

Imo,你應該也有'XXXParameter param = new XXXParameter();'可用,然後使用'XXXParameter.setObjA(objA)'; etc ... – satibel 2016-12-23 10:25:06

1

創建一個bean類,並設置所有參數(setter方法)並將該bean對象傳遞給該方法。

0

如果您傳遞的參數過多,則嘗試重構該方法。也許它正在做很多事情,這是不應該做的。如果不是這種情況,請嘗試用一個類代替參數。通過這種方式,您可以將所有內容封裝在單個類實例中,並傳遞實例而不是參數。

21

如果有許多可選參數,你可以創建流暢的API:

... .datesBetween(from(date1).to(date2)) ... 
+2

+1對於頗有創意的想法,我不得不承認! – 2010-12-19 14:14:09

+1

如果每個參數都是必需的,不是可選的? – Emerald214 2011-08-12 16:04:48

+1

您也可以使用這種方式的默認參數。另外,[builder pattern](http://en.wikipedia.org/wiki/Builder_pattern)與流暢的界面相關。我認爲這應該是真正的答案。除了將長構造器分解爲可選的更小的初始化方法之外。 – 2012-05-04 19:04:30

0
  • :與方法

    exportWithParams().datesBetween(date1,date2) 
            .format("xml") 
            .columns("id","name","phone") 
            .table("angry_robots") 
            .invoke(); 
    

    使用靜態導入,您可以創建內部流暢的API鏈取代單一的方法

    看看你的代碼,看看爲什麼所有這些參數都被傳入。有時候可以重構方法本身。

  • 使用地圖會使您的方法易受攻擊。如果有人使用你的方法拼寫錯了一個參數名稱,或者在你的方法需要一個UDT的地方張貼一個字符串?

  • 定義Transfer Object。它至少會爲您提供類型檢查;甚至有可能在使用時而不是在你的方法中執行一些驗證。

3

代碼完成*建議兩件事情:

  • 「限制常規的參數的數量約七戰七是人們理解一個神奇的數字。」(第108頁)。
  • 「將參數放入輸入 - 修改 - 輸出順序...如果多個程序使用相似的參數,請按照一致的順序放置相似的參數」(第105頁)。
  • 將狀態或錯誤變量放在最後。
  • tvanfosson所述,只傳遞例程需要的結構化變量(對象)的部分。也就是說,如果您在函數中使用大部分結構化變量,那麼只需傳遞整個結構,但請注意,這在某種程度上會促進耦合。

*第一版,我知道我應該更新。另外,從OOP開始變得更受歡迎時編寫第二版開始,這些建議可能會發生一些變化。

5

構建對象時,這通常是一個問題。

在這種情況下,使用構建器對象模式,如果您有大量參數並且並不總是需要所有這些參數,

您還可以將其調整爲方法調用。

它也增加了很多可讀性。

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()方法中。

+0

如果你的許多領域都是「最終」,你會推薦什麼?這是讓我無法編寫幫助函數的主要原因。我想我可以將這些字段設置爲私有的,並確保我不會在該類的代碼中錯誤地修改它們,但我希望更優雅。 – ragerdl 2017-04-05 18:37:20

相關問題