Java方法參數太多怎麼辦—Part5—方法命名

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

目錄

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

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

在前面文章(“Java方法參數太多怎麼辦”系列之四)中,討論瞭如何使用方法重載減少方法和構造函數的參數,指出了這種方式的一些不足並建議使用多個不同的函數名代替重載。本文將深入討論如何通過函數命名解決參數過多的問題,並且可以彌補方法重載的一些不足。

從減少參數的角度來看,方法重載的核心問題在於:當參數過多時,相同名字的方法到底可以重載多少次?當其中一些參數的類型相同時尤其如此。舉個例子,我定義一個包含三個String屬性的類,想通過三個構造函數分別初始化不同的屬性。這樣完全沒法用重載來解決這個問題。通過試驗發現,只有查閱註釋(Javadoc)才能確定構造函數到底初始化了哪個String屬性。不使用構造函數重載,通過定義不同的方法名能提高代碼的可讀性。

下面的示例代碼展示瞭如何調用其它類的方法實例化Person。這些方法都有着長長的名字,詳細描述了所需的參數。這意味着需要的方法註釋更少,對於調用方法的開發者代碼更具可讀性,相比方法重載支持的參數組合方式更多也更具前景。

通過函數名描述Person實例化示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
publicPerson createPersonWithFirstAndLastNameOnly(finalString firstName,finalString lastName)
 {
 // implementation goes here ...
 }
 
publicPerson createEmployedHomeOwningFemale(finalFullName name,finalAddress address)
 {
 // implementation goes here ...
 }
 
publicPerson createEmployedHomeOwningMale(finalFullName name,finalAddress address)
 {
 // implementation goes here ...
 }
 
publicPerson createUnemployedHomeOwningFemale(finalFullName name,finalAddress address)
 {
 // implementation goes here ...
 }
 
publicPerson createEmployedRentingMale(finalFullName name,finalAddress address)
 {
 // implementation goes here ...
 }

上面的示例代碼中,使用較長的描述性方法名可以讓開發者更好地瞭解調用方法所需的參數。當然,我可以寫更多的這樣方法來覆蓋不同的參數排列組合,這裏只是列出其中的一個小集合。請注意我在示例代碼中使用了參數對象(在之前的文章中定義過的Fu、lName和Address)來進一步減少調用方法時所需的參數。

上面的示例代碼展示了在實例化過程中,通過不同的描述性方法名來顯示哪些參數需要傳遞。在一些情況下,哪些參數可以由方法名知道無需傳遞。新手可能會覺得這種方法無法用於對象實例化和初始化,理由是Java中類的構造函數必須與類同名相同。這意味着構造函數僅能通過同名函數重載。幸運的是,Josh Bloch在每一版Effective Java的第一章都會解釋這個問題。按照Bloch的說法,我們可以使用靜態初始化工廠實例化類。這樣做的好處之一是,我們可以用任意合理的方式命名方法。

下列的代碼爲我們展示了靜態初始化工廠的功能。當我實現這些功能時,我喜歡定義一個或幾個只提供靜態初始化工廠調用的私有(標記爲private,不能被其它類調用)構造函數。只有我定義的類必須使用這樣的方式初始化,其他人使用靜態初始化工廠初始化會更加簡單。這樣把參數很多的構造函數隱藏了起來,可以讓這些構造函數的聲明完全滿足需求。具體地說,如果構造函數要求參數傳入空值,可以通過定義不同的靜態初始化工廠方法解決。這樣調用者不必特意爲這些參數傳遞入空值,而是由靜態初始化工廠方法代替客戶爲構造函數傳遞空值。簡而言之,靜態初始化工廠方法爲用戶呈現了一個更整潔、更友善的接口並且隱藏了類定義中的帶有過多參數構造函數。由於構造函數無法自定義方法名,無法直接使用重構解決參數過多的問題。如果需要,靜態初始化工廠方法可以接受“原始”類型參數並在內部把它轉爲通用類型和參數對象,這是靜態初始化工廠方法的另一個優勢。以上所說的功能在下面的示例代碼中一一列舉了出來:

靜態初始化工廠驗證

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
/**
 * 構造函數標記爲private,因爲只有內部構造器會調用它創建實例。
*
 * @param newName Name of this person.
 * @param newAddress Address of this person.
 * @param newGender Gender of this person.
 * @param newEmployment Employment status of this person.
 * @param newHomeOwner Home ownership status of this person.
 */
 privatePerson(
 finalFullName newName,finalAddress newAddress,
 finalGender newGender,finalEmploymentStatus newEmployment,
 finalHomeownerStatus newHomeOwner)
 {
 this.name = newName;
 this.address = newAddress;
 this.gender = newGender;
 this.employment = newEmployment;
 this.homeOwnerStatus = newHomeOwner;
}
 
publicstaticPerson createInstanceWithNameAndAddressOnly(
 finalFullName newName,finalAddress newAddress)
 {
 returnnewPerson(newName, newAddress,null,null,null);
 }
 
publicstaticPerson createEmployedHomeOwningFemale(
 finalFullName newName,finalAddress newAddress)
 {
 returnnewPerson(
 newName, newAddress, Gender.FEMALE, EmploymentStatus.EMPLOYED, HomeownerStatus.HOME_OWNER);
 }
 
publicstaticPerson createEmployedHomeowningMale(
 finalFullName newName,finalAddress newAddress)
 {
 returnnewPerson(
 newName, newAddress, Gender.MALE, EmploymentStatus.EMPLOYED, HomeownerStatus.HOME_OWNER);
 }
 
publicstaticPerson createUnemployedMaleRenter(
 finalFullName newName,finalAddress newAddress)
 {
 returnnewPerson(
 newName, newAddress, Gender.MALE, EmploymentStatus.NOT_EMPLOYED, HomeownerStatus.RENTER);
 }
 
publicstaticPerson createPersonWithFirstNameLastNameAndAddress(
 finalName newFirstName,finalName newLastName,finalAddress newAddress)
 {
 returnnewPerson(
 newFullName.FullNameBuilder(newLastName, newFirstName).createFullName(),
 newAddress,null,null,null);
 }
 
publicstaticPerson createPersonWithFirstNameLastNameAndAddress(
 finalString newFirstName,finalString newLastName,finalAddress newAddress)
 {
 returnnewPerson(
 newFullName.FullNameBuilder(newName(newLastName),newName(newFirstName)).createFullName(),
 newAddress,null,null,null);
 }

如上面的示例代碼所示,這些方法具有較高的可讀性並且不會要求傳入很多參數。最後兩個示例結合了方法重載和靜態初始化工廠。

 方法命名的好處和優點

相比簡單的方法重載,恰如其分地定義描述了所需參數信息的方法名有一些優點。方法名可以根據方法的預期和假設定製,調用方法函數的代碼的意圖也更加明顯。就像上面的示例代碼那樣,通過方法名可以看出哪些參數不必直接提供,因爲它們被設定爲方法的一部分(這種意圖通過方法名而不是來註釋來傳達)。

有一個優勢在本文並沒有明確地說明:相比方法重載,方法名可以包含參數的單位或者其它背景信息。舉個例子,可以用接收整數的方法setWholeLengthInMeters(int)和接收小數的方法setFractionalLengthInFeet(double)來代替需要同時接收整數和小數的方法setLength()。

 方法命名的代價和缺點

雖然使用不同名字的方法實例化和靜態初始化工廠相比方法重載具有一些明顯的優勢,但不幸的是方法命名也具有方法重載的一些問題。一個相同的問題是,爲了支持可能會用到的參數所有排列組合,需要編寫大量的方法。拿上面的例子來說,爲包含性別、房屋所有權和工作狀況的所有組合方式就需要8個方法(2的3次方)。假設任意一個單獨的參數值有2種以上可能,那麼爲了覆蓋參數值的不同組合方法名數量就需要隨着參數可能的數量增長。當然,參數值具有無限可能的情況不能用不同的方法名定義所有的可能,只能傳遞它的值而不是在方法中設定。

雖然描述性的方法名非常易於理解,但是用戶在調用方法時可能會需要在長長的方法列表中跋涉。因此,方法命名可能導致由於方法過多而降低整體的可讀性。另外,一些人可能不喜歡長長的方法名所據過多的屏幕空間。我個人並不介意長函數名,我認爲它們爲提高可讀性在屏幕上顯示額外的文本是值得的。有IDE和語言規範的幫助,基本不會在輸入長方法名時出錯。爲開發者配備多個大屏監控器也使得長方法名的不是那麼讓人煩惱。

結論

方法名可以用來向用戶傳遞重要信息。在努力淨化特定方法的參數個數(包括減少參數個數)時,通過適當的方法命名可以瞭解方法的默認設置、哪些參數是不需要的,還可以解釋其它參數的排列順序及其特徵。

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