在前一篇文章中,我關注了一些關於構造函數或方法參數過多的問題。文中我討論了用自定義類型代替基本、內置類型以獲得良好的可讀性和安全性。然而這並不能減少參數的數量。這次,我將用參數對象方法給構造函數和方法的參數“瘦身”。
通常你會看到一組特定參數,它們關係緊密並且總是一起傳給方法或構造函數,有可能好幾個函數都使用這一組參數。這些函數可能屬於同一個類,也可能屬於不同的類。
這時,《重構》這本書中介紹的“引入參數對象”的方法能很好地解決問題。該方法可以描述爲運用一個對象封裝這些參數,再以該對象取代他們。本文就是要演示這一“重構”。
爲演示好“引入參數對象”這一重構方法,我們首先看下上篇文章曾使用過的代碼示例。
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和腳本語言,其中大量繁瑣的步驟都能自動完成。當然,關於參數對象還有一個更小的爭論:參數對象可能被濫用。如果一個開發者純粹爲了減少參數數量,把聯繫不緊的幾個參數強捆在一個類中這肯定是行不通的,在可讀性上甚至適得其反。
總結
參數對象通過恰當封裝聯繫緊密的參數來減少方法和構造函數的參數數量。它們易於實現,能顯著提高方法和構造函數在參數傳遞時的可讀性和安全性。正如我上篇文章所說,參數對象如果結合自定義類型等一起使用效果會更好。