Java方法參數太多怎麼辦—Part 1—自定義類型

我認爲構造函數和方法過長的傳遞參數列表是一種紅色警告(”red flag“)。在開發過程中,從邏輯的和功能的角度來看並非錯誤,但是通常意味着現在或者將來犯錯誤的可能性更高。通過閱讀一系列文章,我發現一些解決參數列表過長的辦法,或者至少這些辦法可以減少參數個數、增強代碼的可讀性並降低發生錯誤的概率。任何解決問題的辦法都具有優點和缺點。本文旨在通過使用自定義類型改進長參數方法和構造函數代碼的可讀性和安全性。

方法和構造函數的參數列表過長會產生一系列的障礙。大量的參數不僅使得代碼看起來冗餘,而且使得調用起來會很困難。同時,它又容易導致因疏忽而產生的參數移位(參數類型沒變,但是因爲位置改變值卻改變了)。這些錯誤在特定情況下難以發現。幸運地是大多時候我們不必處理另一個參數過長的缺點:Java虛擬機(JVM)通過編譯時報告錯誤(compile-time error限制了方法的參數數量

使用自定義類型一方面可以減少構造函數和方法的傳參個數,另一方面又可以增強參數列表的可讀性並且降低參數位置放錯的可能性。自定義類型的實現方式包括Data Transfer ObjectsJavaBeansValue ObjectsReference Objects或者其他(在Java中經典的實現方式:枚舉)自定義類型。

下面來看一個例子,該方法包含多個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
24
25
26
27
28
29
30
31
/**
   * Instantiate a Person object.
   *
   * @param lastName
   * @param firstName
   * @param middleName
   * @param salutation
   * @param suffix
   * @param streetAddress
   * @param city
   * @param state
   * @param isFemale
   * @param isEmployed
   * @param isHomeOwner
   * @return
   */
  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
  }

可以發現很容易搞混參數的順序,然後把它們放錯位置。我通常更樂意通過改變參數類型來做一些提高,以期減少參數個數。下面這些代碼展示瞭如何使用自定義類型。

三個名字可以改爲自定義類型Name,而不是使用String。

Name.java

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
package dustin.examples;
 
/**
 * Name representation.
 *
 * @author Dustin
 */
public final class Name
{
   private final String name;
 
   public Name(final String newName)
   {
      this.name = newName;
   }
 
   public String getName()
   {
      return this.name;
   }
 
   @Override
   public String toString()
   {
      return this.name;
   }
}

稱呼和後綴也可以替換爲如下兩段代碼所示的自定義類型:

Salutation.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package dustin.examples;
 
/**
 * Salutations for individuals' names.
 *
 * @author Dustin
 */
public enum Salutation
{
   DR,
   MADAM,
   MISS,
   MR,
   MRS,
   MS,
   SIR
}

Suffix.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package dustin.examples;
 
/**
 * Suffix representation.
 *
 * @author Dustin
 */
public enum Suffix
{
   III,
   IV,
   JR,
   SR
}

其他的代碼也可使用自定義類型。下面通過枚舉的方式替換boolean型,以提高代碼的可讀性:

Gender.java

1
2
3
4
5
6
7
8
9
10
11
12
package dustin.examples;
 
/**
 * Gender representation.
 *
 * @author Dustin
 */
public enum Gender
{
   FEMALE,
   MALE
}

EmploymentStatus.java

1
2
3
4
5
6
7
8
9
10
11
12
package dustin.examples;
 
/**
 * Representation of employment status.
 *
 * @author Dustin
 */
public enum EmploymentStatus
{
   EMPLOYED,
   NOT_EMPLOYED
}

HomeOwnerStatus.java

1
2
3
4
5
6
7
8
9
10
11
12
package dustin.examples;
 
/**
 * Representation of homeowner status.
 *
 * @author Dustin
 */
public enum HomeownerStatus
{
   HOME_OWNER,
   RENTER
}

此人的地址信息可以通過如下代碼所示的自定義類型作爲參數進行傳遞。
StreetAddress.java

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
package dustin.examples;
 
/**
 * Street Address representation.
 *
 * @author Dustin
 */
public final class StreetAddress
{
   private final String address;
 
   public StreetAddress(final String newStreetAddress)
   {
      this.address = newStreetAddress;
   }
 
   public String getAddress()
   {
      return this.address;
   }
 
   @Override
   public String toString()
   {
      return this.address;
   }
}

City.java

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
package dustin.examples;
 
/**
 * City representation.
 *
 * @author Dustin
 */
public final class City
{
   private final String cityName;
 
   public City(final String newCityName)
   {
      this.cityName = newCityName;
   }
 
   public String getCityName()
   {
      return this.cityName;
   }
 
   @Override
   public String toString()
   {
      return this.cityName;
   }
}

State.java

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
package dustin.examples;
 
/**
 * Simple representation of a state in the United States.
 *
 * @author Dustin
 */
public enum State
{
   AK,
   AL,
   AR,
   AZ,
   CA,
   CO,
   CT,
   DE,
   FL,
   GA,
   HI,
   IA,
   ID,
   IL,
   IN,
   KS,
   KY,
   LA,
   MA,
   MD,
   ME,
   MI,
   MN,
   MO,
   MS,
   MT,
   NC,
   ND,
   NE,
   NH,
   NJ,
   NM,
   NV,
   NY,
   OH,
   OK,
   OR,
   PA,
   RI,
   SC,
   SD,
   TN,
   TX,
   UT,
   VA,
   VT,
   WA,
   WI,
   WV,
   WY
}

通過使用自定義類型,開始展示的方法的代碼變得更加易讀易懂,同時更不容易出錯了。下面是使用自定義類型改寫後的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public Person createPerson(
   final Name lastName,
   final Name firstName,
   final Name middleName,
   final Salutation salutation,
   final Suffix suffix,
   final StreetAddress address,
   final City city,
   final State state,
   final Gender gender,
   final EmploymentStatus employment,
   final HomeownerStatus homeowner)
{
   // implementation goes here
}

在上面這段代碼中,編譯器會不允許使用先前那種很多String、boolean參數的方式。這樣來減少了放錯參數順序的可能性,從而幫助到開發者。但是三個名字仍然存在一個潛在的問題,因爲代碼的調用者依然容易搞亂它們的順序。出於這種擔心,需要爲此專門定義FirstName、LastName、MiddleName 三種類型。但通常我喜歡使用一個自定義類型,裏面放置上述三個名字作爲新類的屬性。當然那屬於後來即將講解的解決Java參數過長問題的文章的內容了。

使用自定義類型的好處和優點


提高了代碼的可讀性,爲代碼的維護者和API調用者提供了便利。提高了在編寫代碼時開發環境(IDE)參數匹配的能力,沒有什麼比使用自定義類型的編譯環境檢查更能幫助開發環境的了。通常來說,我更喜歡儘可能地把這些自動檢查由運行環境移到編譯環境,並且把他們定義爲可直接調用的靜態類型而非普通類型。

進一步說,自定義類型的存在也使我們將來爲它添加更多細節變得更加容易。例如:將來我也許會爲方法創建人添加一個全名或者把其他的狀態放在枚舉器當中,同時這樣也不會改變自定義類型的原有面貌。這些都是使用String類型無法完成的。這樣做的另一個潛在優點就是使用自定義類型擁有更大的靈活性,可以在不改變原有類型面貌的情況下改變實現方式。這些自定義類型(不包括枚舉器)能夠被擴展(String則不具備),並且可以在不改變它的類型的情況下靈活添加自定義細節。

自定義類型的代價和缺點

普遍存在缺點之一,就是開始需要額外的實例化和佔用內存。例如:Name類需要實例化,而且封裝了String。我認爲之所以有人會持有這種觀點,更多的是因爲他從一種尚不夠成熟的所謂的最優化角度,而非實用合理的角度來衡量性能問題。當然也有這種情況存在,即:額外實例化這些類型花費了太多的代價並且不能證明增強可讀性和編譯能力所帶來的好處。然而大多時候這種額外的開銷都是可以承受的,不會產生什麼可見的壞影響。如果大多數時候使用自定義類型而不用String或者boolean會產生性能問題,這真的讓人難以置信。
另一些人認爲使用自定義類型而非內置類型需要付出額外的寫和測試代碼的努力。然而,正如我的這篇文章的代碼所顯示的那樣,這些非常簡單的類和枚舉器寫和測試起來並不難。使用一個優秀的開發環境和一門靈活的編程語言(如:Groovy),編寫和測試會更加容易而且通常可以自動完成。

結論

我喜歡使用自定義類型來提高代碼的可讀性,將更多的編譯檢查負擔轉給編譯器。我不喜歡這種傳參方式的最大原因在於:這種方法本身只是提高了擁有過長參數列表的構造函數和方法的可讀性卻並沒有減少實際需要傳遞的參數數量,代碼的調用者依然需要寫那些笨拙的客戶端代碼來調用構造函數和方法。因此,我通常使用其它技術而不是增加自定義類型來解決向方法傳遞參數過長的問題。這些技術將在接下來的文章裏講述。

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