Java方法參數太多怎麼辦—Part4—重載

本文由 ImportNew - 韓遠青 翻譯自 dzone。歡迎加入翻譯小組。轉載請參見文章末尾的要求。

目錄

  1. 自定義類型
  2. 引入參數對象
  3. Builder模式
  4. 重載

本文是這個系列的第四篇文章,介紹了通過重載解決參數過多的問題。如果你也希望參與類似的系列文章翻譯,可以加入我們的Java開發技術翻譯小組。

在Java編程中,如果一個方法帶太多的參數被會給調用者帶來很多困擾。調用者必須考慮是否按照正確的參數順序給傳入合適的值。在前面的文章中,先後探討了通過自定義類型參數對象Builder模式來解決這個問題。還有一種方法,也是今天探討的主題——通過方法重載來應對各種不同需求。與往常一樣,在文章最後我會對重載的優缺點進行總結。

Java支持方法重載,可以通過方法簽名來區分同名方法之間是否重載。請注意:方法返回值不能作爲判斷是否重載的依據(請大家注意“重寫 Override”和“重載 Overload”之間的區別)。

實際編程中可能會有很多理由讓你使用重載。其中一個是爲不同類型實現同樣的功能(特別是方法不適合通過泛型支持多種類型,或者編寫代碼的環境不支持泛型)。String.valueOf(boolean)String.valueOf(char)String.valueOf(double)String.valueOf(long)String.valueOf(java.lang.Object)以及其它類型String.valule()方法就是方法重載的一個簡單示例。

選擇重載的另一個原因是方便客戶調用時“按需分配”。客戶能根據需要的參數選擇相應實現方法,這樣可以避免傳一個或多個null值或可選參數。例如java.util包中的Date類就使用重載方法,可供選擇的構造函數多種多樣,像是Date(int, int, int)Date(int, int, int, int, int)Date(int, int, int, int, int, int, int)等。

重載構造函數會產生多個不同的構造函數,每個構造函數接收的參數數量各不相同。這些構造函數又叫做重疊構造函數,一些人稱之爲“反模式”。事實上,在Effective Java第二版的第二條中,作者Josh Bloch提到:“之所以使用Builder模式,其中一個原因就是重疊構造器模式本身有缺陷。”順便說一下,針對實現前面提到的目標,Date類也提供了一些重載的構造函數,支持從String類型構造Date

自己編寫類時,可以通過重載構造函數和方法,只接收必需的參數或者儘可能減少必須的參數。下面的示例代碼中,首先展示了一段問題代碼——方法接受的參數過多;接下來展示了一些通過重載精簡參數後的若干備選方案。爲了便於討論,我們假定重寫過的方法簽名中所有參數都是必須的,在調用時都必須填寫。示例代碼在註釋中對方法重構過程中的一些假設進行了說明。

函數參數過多及重載改進示例代碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/**
 * 生成Person實例。
 * 此方法要求初始化Person實例時,所有屬性都必須有值。
 * 可選或不可用屬性傳null。
 *
 * @param lastName
 * @param firstName
 * @param middleName
 * @param salutation
 * @param suffix
 * @param streetAddress
 * @param city
 * @param state
 * @param isFemale
 * @param isEmployed
 * @param isHomeOwner
 * @return A Person object.
 */
public Person createPerson(
   final String lastName,
   final String firstName,
   final String middleName,
   final String salutation,
   final String suffix,
   final String streetAddress,
   final String city,
   final String state,
   final boolean isFemale,
   final boolean isEmployed,
   final boolean isHomeOwner)
{
   // implementation goes here...
}
 
/**
 * 生成Person實例。
 * Person初始化必須提供姓名和地址信息。
 * 對其它沒有初始化的Person屬性不賦初值。
 *
 * @param lastName
 * @param firstName
 * @param streetAddress
 * @param city
 * @param state
 * @return 返回的Person實例對中間名、性別、僱員狀態或房屋所有權均不賦初值。
 */
public Person createPerson(
   final String lastName,
   final String firstName,
   final String streetAddress,
   final String city,
   final String state)
{
   // implementation goes here...
}
 
/**
 * 生成的Persn實例沒有中間名但指定了房屋所有權狀態。
 * 該方法返回的所有Person實例都假定爲已僱傭女性,但不提供地址信息。
 *
 * @param lastName
 * @param firstName
 * @param homeOwnerStatus
 * @return Person實例,包含姓名和房屋所有權狀態,並且假定爲已僱傭女性、不包含地址信息。
 */
public Person createPerson(
   final String lastName,
   final String firstName,
   final boolean homeOwnerStatus)
{
   // implementation goes here...
}

示例代碼中的註釋說明了各方法間的區別。第一個方法要求初始化Person實例時,所有屬性都必須賦值。如果碰到有一些可選參數或參數不可用,參數傳入null(比如這個人可能沒有中間名middle name,或者某種情況下無需考慮中間名)。第二種方法重載不要求爲所有屬性賦值,返回的Person實例對沒有設置的參數不進行賦值。

第三種方法重載的主要特點是一些屬性不提供參數賦值。例如,方法假定實例化的Person都是女性且都是僱員。然而,這三種方法不能實例化一個男性或沒有僱傭關係的Person。這從側面說明了簡單地使用方法重載來處理參數過多問題的一個短板(即只能通過參數個數和不同類型重載名稱相同方法,這裏請注意與重寫Override區別)。

雖然示例代碼列中沒有提到構造函數,但其中的觀點和方法對構造函數同樣適用。同樣地,構造函數重載和方法重載具有共同的優點與不足。

重載的好處和優點

在Java中進行方法重載看起來很好理解,在其它的一些編程語言比如C/C++C#中,方法重載也非常普遍。特別是遇到可選參數時,使用方法重載會十分有效。在上面的示例中,使用方法重載移除中間名參數要比假定爲所有僱員都是女性更好。如果中間名、性別、僱傭情況確實可選,最好不要多此一舉爲它們設定特殊默認值。

重載的代價和缺點

適當的方法重載非常有用,但同時使用起來也要格外小心。 Learning the Java Language trail系列教程中對類和對象課程方法定義提到:要儘量少用方法重載,避免降低代碼的可讀性。

甚至在上面三個簡單示例中也可以發現,方法重載會明顯降低代碼的可讀性。在前文的示例中,開發者在閱讀和使用代碼時一方面要認真閱讀並且準確理解註釋,另一方面要深入到方法實現中才能發現各重載方法的區別。對編譯器而言,如果存在多個版本的重載方法很難做到“具體問題具體分析”。

在示例代碼中,方法註釋必須清楚地解釋每個重載方法進行的假設。正如剛纔說的那樣,如果代碼的作者不屑於寫好註釋,那麼這些重載的方法可能過時、不準確以致讓別人不知所云。顯然,爲不同的方法取不同的名字效果會更好,至少方法名可以爲理解方法的功能提供一些線索,而不僅僅只依靠註釋。使用不同的方法名會在後面的文章中進行討論。

代碼示例中也體現了多個相同類型參數的重載方法的一個嚴重限制。第三個方法只提供了一個boolean參數,只有通過閱讀註釋和參數名才能知道這個方法只關心是否擁有房產,並不關心性別和僱傭狀態。在只提供相同的方法名時,想要通過boolean參數代表Person的性別、僱傭情況等其他特徵是不可能的。這種情況下,由於方法簽名相同使用方法重載是行不通的。這再一次表明,必須採用不同的方法名來具體區分boolean參數要表達的隱含內容。

突破方法重載限制的另一個方法是單獨或統一使用自定義類型和參數對象,提供各種版本的重載方法來接收不同的自定義類型組合。下面的示例代碼展示瞭如何使用自定義類型進行方法重載。這些重載方法接收兩個string參數和一個適用於三種情況的自定義類型參數,這樣就不必使用相同的boolean參數了。

使用自定義類型改進方法、構造函數重載

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public Person createPerson(
   final String lastName,
   final String firstName,
   final HomeownerStatus homeOwnership)
{
   // implementation goes here...
}
 
public Person createPerson(
   final String lastName,
   final String firstName,
   final Gender gender)
{
   // implementation goes here...
}
 
public Person createPerson(
   final String lastName,
   final String firstName,
   final EmploymentStatus employmentStatus)
{
   // implementation goes here...
}

關於方法重載的缺點,我想再補充一下。使用方法重載應對有構造函數或方法參數過多會帶來大量的維護工作。在任何情況下,類(構造函數)屬性或方法參數的增加、刪除甚至是改變都會帶來額外的審查或代碼變更。

總結

方法重載確實有其適用的地方,並且可以提高方法和構造函數的可讀性。但是在我看來,方法重載不及前面幾篇中(自定義類型參數對象Builder模式)提到的方法,比起即將要介紹的一些方法(比如方法命名)也用得更少。通過結合其他方法可以改善方法重載的一些限制和不足,比如使用自定義類型和參數對象能夠顯著改善重載方法或構造函數對細粒度問題的處理能力。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章