Java方法參數太多怎麼辦—Part 2—引入參數對象

前一篇文章中,我關注了一些關於構造函數或方法參數過多的問題。文中我討論了用自定義類型代替基本、內置類型以獲得良好的可讀性和安全性。然而這並不能減少參數的數量。這次,我將用參數對象方法給構造函數和方法的參數“瘦身”。

通常你會看到一組特定參數,它們關係緊密並且總是一起傳給方法或構造函數,有可能好幾個函數都使用這一組參數。這些函數可能屬於同一個類,也可能屬於不同的類。

這時,《重構》這本書中介紹的“引入參數對象”的方法能很好地解決問題。該方法可以描述爲運用一個對象封裝這些參數,再以該對象取代他們。本文就是要演示這一“重構”。

爲演示好“引入參數對象”這一重構方法,我們首先看下上篇文章曾使用過的代碼示例。

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
}

前面說過,這種傳統方法對調用者來說非常乏味。不但因爲沒有類型安全保證導致傳參時容易搞混,而且代碼可讀性也不是很理想。幸運的是,可以通過“引入參數對象”方法重構示例代碼。“names”型參數能合併寫進一個“FullName”類,同理”address”型參數也一樣。其他剩餘參數因爲相互聯繫不那麼緊密沒法合併到一個新的類中。

在引入參數對象重構後參數數量減少了,方法調用相比以前也變得更加簡單。這將在下面的代碼中得以體現。

1
2
3
4
5
6
7
8
9
public Person createPerson(
   final FullName fullName,
   final Address address,
   final boolean isFemale,
   final boolean isEmployed,
   final boolean isHomeOwner)
{
   return new Person();
}

上面的示例中只有5個參數,這意味着可讀性更強和調用也更方便。從類型角度看也更爲安全,因爲不會將名字和地址類型的字符串混淆。遺憾的是,三個布爾型參數依然存在這種隱患。下面代碼展示了FullName 和Address類:

FullName.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
61
package dustin.examples;
 
/**
 * Full name of a person.
 *
 * @author Dustin
 */
public final class FullName
{
   private final String lastName;
   private final String firstName;
   private final String middleName;
   private final String salutation;
   private final String suffix;
 
   public FullName(
      final String newLastName,
      final String newFirstName,
      final String newMiddleName,
      final String newSalutation,
      final String newSuffix)
   {
      this.lastName = newLastName;
      this.firstName = newFirstName;
      this.middleName = newMiddleName;
      this.salutation = newSalutation;
      this.suffix = newSuffix;
   }
 
   public String getLastName()
   {
      return this.lastName;
   }
 
   public String getFirstName()
   {
      return this.firstName;
   }
 
   public String getMiddleName()
   {
      return this.middleName;
   }
 
   public String getSalutation()
   {
      return this.salutation;
   }
 
   public String getSuffix()
   {
      return this.suffix;
   }
 
   @Override
   public String toString()
   {
      return  this.salutation + " " + this.firstName + " " + this.middleName
            + this.lastName + ", " + this.suffix;
   }
}

Address.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
package dustin.examples;
 
/**
 * Representation of a United States address.
 *
 * @author Dustin
 */
public final class Address
{
   private final String streetAddress;
   private final String city;
   private final String state;
 
   public Address(final String newStreetAddress, final String newCity, final String newState)
   {
      this.streetAddress = newStreetAddress;
      this.city = newCity;
      this.state = newState;
   }
 
   public String getStreetAddress()
   {
      return this.streetAddress;
   }
 
   public String getCity()
   {
      return this.city;
   }
 
   public String getState()
   {
      return this.state;
   }
 
   @Override
   public String toString()
   {
      return this.streetAddress + ", " + this.city + ", " + this.state;
   }
}

儘管代碼得到了改進,仍然有一些問題有待結局。尤其是原來的方法參數包含了3個布爾類型,彼此之間很容易混淆。儘管字符串類型參數被重構到了兩個新類,但是這兩個類中還是包含了很多的字符串。這種情況就需要爲引入的參數對象用自定義類型重構。使用上篇中展示的自定義類型,重構以後的參數如下面代碼所示:

1
2
3
4
5
6
7
8
9
public Person createPerson(
   final FullName fullName,
   final Address address,
   final Gender gender,
   final EmploymentStatus employment,
   final HomeownerStatus homeownerStatus)
{
   // implementation goes here
}

現在方法參數個數變少了,並且類型各不相同。IDE和JAVA編譯器能很有效的確保接口被正確使用。在上面示例中使用 FullName 和Address類,重構以後的代碼如下:

FullName.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
61
package dustin.examples;
 
/**
 * Full name of a person.
 *
 * @author Dustin
 */
public final class FullName
{
   private final Name lastName;
   private final Name firstName;
   private final Name middleName;
   private final Salutation salutation;
   private final Suffix suffix;
 
   public FullName(
      final Name newLastName,
      final Name newFirstName,
      final Name newMiddleName,
      final Salutation newSalutation,
      final Suffix newSuffix)
   {
      this.lastName = newLastName;
      this.firstName = newFirstName;
      this.middleName = newMiddleName;
      this.salutation = newSalutation;
      this.suffix = newSuffix;
   }
 
   public Name getLastName()
   {
      return this.lastName;
   }
 
   public Name getFirstName()
   {
      return this.firstName;
   }
 
   public Name getMiddleName()
   {
      return this.middleName;
   }
 
   public Salutation getSalutation()
   {
      return this.salutation;
   }
 
   public Suffix getSuffix()
   {
      return this.suffix;
   }
 
   @Override
   public String toString()
   {
      return  this.salutation + " " + this.firstName + " " + this.middleName
            + this.lastName + ", " + this.suffix;
   }
}

Address.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
package dustin.examples;
 
/**
 * Representation of a United States address.
 *
 * @author Dustin
 */
public final class Address
{
   private final StreetAddress streetAddress;
   private final City city;
   private final State state;
 
   public Address(final StreetAddress newStreetAddress, final City newCity, final State newState)
   {
      this.streetAddress = newStreetAddress;
      this.city = newCity;
      this.state = newState;
   }
 
   public StreetAddress getStreetAddress()
   {
      return this.streetAddress;
   }
 
   public City getCity()
   {
      return this.city;
   }
 
   public State getState()
   {
      return this.state;
   }
 
   @Override
   public String toString()
   {
      return this.streetAddress + ", " + this.city + ", " + this.state;
   }
}

現在,所有的示例都使用了獨立的公共類。如果參數對象類位於同一個包的作用域中,就能將參數對象用於方法和構造函數之間傳遞信息,即使只能達成這一點也是非常有用的。另外在一些情況下,嵌套類也能當參數對象使用。

引入參數對象的好處與優點

最明顯好處在於參數對象減少了構造函數或方法的傳參數量。相關的參數一起封裝更容易確定傳遞給構造函數和方法的是什麼類型的參數。參數數量減少帶給開發者的好處是顯而易見的。

參數對象跟上篇文章討論過的自定義類型有一個相同的好處:可以方便地爲參數對象添加額外的行爲和特徵。 如用一個Address類來驗證地址信息,而不是使用一堆字符串。

引入參數對象的代價與缺點

參數對象的主要缺點是需要額外的工作來設計、實現和測試相關類。但是,如果我們藉助IDE和腳本語言,其中大量繁瑣的步驟都能自動完成。當然,關於參數對象還有一個更小的爭論:參數對象可能被濫用。如果一個開發者純粹爲了減少參數數量,把聯繫不緊的幾個參數強捆在一個類中這肯定是行不通的,在可讀性上甚至適得其反。

總結

參數對象通過恰當封裝聯繫緊密的參數來減少方法和構造函數的參數數量。它們易於實現,能顯著提高方法和構造函數在參數傳遞時的可讀性和安全性。正如我上篇文章所說,參數對象如果結合自定義類型等一起使用效果會更好。

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