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和脚本语言,其中大量繁琐的步骤都能自动完成。当然,关于参数对象还有一个更小的争论:参数对象可能被滥用。如果一个开发者纯粹为了减少参数数量,把联系不紧的几个参数强捆在一个类中这肯定是行不通的,在可读性上甚至适得其反。

总结

参数对象通过恰当封装联系紧密的参数来减少方法和构造函数的参数数量。它们易于实现,能显著提高方法和构造函数在参数传递时的可读性和安全性。正如我上篇文章所说,参数对象如果结合自定义类型等一起使用效果会更好。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值