Java方法参数太多怎么办—Part 1—自定义类型

来源:互联网 发布:蜘蛛侠归来 知乎 编辑:程序博客网 时间:2024/04/29 09:38

我认为构造函数和方法过长的传递参数列表是一种红色警告(”red flag“)。在开发过程中,从逻辑的和功能的角度来看并非错误,但是通常意味着现在或者将来犯错误的可能性更高。通过阅读一系列文章,我发现一些解决参数列表过长的办法,或者至少这些办法可以减少参数个数、增强代码的可读性并降低发生错误的概率。任何解决问题的办法都具有优点和缺点。本文旨在通过使用自定义类型改进长参数方法和构造函数代码的可读性和安全性。

方法和构造函数的参数列表过长会产生一系列的障碍。大量的参数不仅使得代码看起来冗余,而且使得调用起来会很困难。同时,它又容易导致因疏忽而产生的参数移位(参数类型没变,但是因为位置改变值却改变了)。这些错误在特定情况下难以发现。幸运地是大多时候我们不必处理另一个参数过长的缺点:Java虚拟机(JVM)通过编译时报告错误(compile-time error)限制了方法的参数数量。

使用自定义类型一方面可以减少构造函数和方法的传参个数,另一方面又可以增强参数列表的可读性并且降低参数位置放错的可能性。自定义类型的实现方式包括Data Transfer Objects、JavaBeans、Value Objects、Reference 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
   */
  publicPerson createPerson(
     finalString lastName,
     finalString firstName,
     finalString middleName,
     finalString salutation,
     finalString suffix,
     finalString streetAddress,
     finalString city,
     finalString state,
     finalboolean isFemale,
     finalboolean isEmployed,
     finalboolean 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
packagedustin.examples;
 
/**
 * Name representation.
 *
 * @author Dustin
 */
publicfinal class Name
{
   privatefinal String name;
 
   publicName(finalString newName)
   {
      this.name = newName;
   }
 
   publicString getName()
   {
      returnthis.name;
   }
 
   @Override
   publicString toString()
   {
      returnthis.name;
   }
}

称呼和后缀也可以替换为如下两段代码所示的自定义类型:

Salutation.java

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

Suffix.java

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

其他的代码也可使用自定义类型。下面通过枚举的方式替换boolean型,以提高代码的可读性:

Gender.java

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

EmploymentStatus.java

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

HomeOwnerStatus.java

1
2
3
4
5
6
7
8
9
10
11
12
packagedustin.examples;
 
/**
 * Representation of homeowner status.
 *
 * @author Dustin
 */
publicenum 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
packagedustin.examples;
 
/**
 * Street Address representation.
 *
 * @author Dustin
 */
publicfinal class StreetAddress
{
   privatefinal String address;
 
   publicStreetAddress(finalString newStreetAddress)
   {
      this.address = newStreetAddress;
   }
 
   publicString getAddress()
   {
      returnthis.address;
   }
 
   @Override
   publicString toString()
   {
      returnthis.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
packagedustin.examples;
 
/**
 * City representation.
 *
 * @author Dustin
 */
publicfinal class City
{
   privatefinal String cityName;
 
   publicCity(finalString newCityName)
   {
      this.cityName = newCityName;
   }
 
   publicString getCityName()
   {
      returnthis.cityName;
   }
 
   @Override
   publicString toString()
   {
      returnthis.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
packagedustin.examples;
 
/**
 * Simple representation of a state in the United States.
 *
 * @author Dustin
 */
publicenum 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
publicPerson createPerson(
   finalName lastName,
   finalName firstName,
   finalName middleName,
   finalSalutation salutation,
   finalSuffix suffix,
   finalStreetAddress address,
   finalCity city,
   finalState state,
   finalGender gender,
   finalEmploymentStatus employment,
   finalHomeownerStatus homeowner)
{
   // implementation goes here
}

在上面这段代码中,编译器会不允许使用先前那种很多String、boolean参数的方式。这样来减少了放错参数顺序的可能性,从而帮助到开发者。但是三个名字仍然存在一个潜在的问题,因为代码的调用者依然容易搞乱它们的顺序。出于这种担心,需要为此专门定义FirstName、LastName、MiddleName 三种类型。但通常我喜欢使用一个自定义类型,里面放置上述三个名字作为新类的属性。当然那属于后来即将讲解的解决Java参数过长问题的文章的内容了。

使用自定义类型的好处和优点


提高了代码的可读性,为代码的维护者和API调用者提供了便利。提高了在编写代码时开发环境(IDE)参数匹配的能力,没有什么比使用自定义类型的编译环境检查更能帮助开发环境的了。通常来说,我更喜欢尽可能地把这些自动检查由运行环境移到编译环境,并且把他们定义为可直接调用的静态类型而非普通类型。

进一步说,自定义类型的存在也使我们将来为它添加更多细节变得更加容易。例如:将来我也许会为方法创建人添加一个全名或者把其他的状态放在枚举器当中,同时这样也不会改变自定义类型的原有面貌。这些都是使用String类型无法完成的。这样做的另一个潜在优点就是使用自定义类型拥有更大的灵活性,可以在不改变原有类型面貌的情况下改变实现方式。这些自定义类型(不包括枚举器)能够被扩展(String则不具备),并且可以在不改变它的类型的情况下灵活添加自定义细节。

自定义类型的代价和缺点

普遍存在缺点之一,就是开始需要额外的实例化和占用内存。例如:Name类需要实例化,而且封装了String。我认为之所以有人会持有这种观点,更多的是因为他从一种尚不够成熟的所谓的最优化角度,而非实用合理的角度来衡量性能问题。当然也有这种情况存在,即:额外实例化这些类型花费了太多的代价并且不能证明增强可读性和编译能力所带来的好处。然而大多时候这种额外的开销都是可以承受的,不会产生什么可见的坏影响。如果大多数时候使用自定义类型而不用String或者boolean会产生性能问题,这真的让人难以置信。
另一些人认为使用自定义类型而非内置类型需要付出额外的写和测试代码的努力。然而,正如我的这篇文章的代码所显示的那样,这些非常简单的类和枚举器写和测试起来并不难。使用一个优秀的开发环境和一门灵活的编程语言(如:Groovy),编写和测试会更加容易而且通常可以自动完成。

结论

我喜欢使用自定义类型来提高代码的可读性,将更多的编译检查负担转给编译器。我不喜欢这种传参方式的最大原因在于:这种方法本身只是提高了拥有过长参数列表的构造函数和方法的可读性却并没有减少实际需要传递的参数数量,代码的调用者依然需要写那些笨拙的客户端代码来调用构造函数和方法。因此,我通常使用其它技术而不是增加自定义类型来解决向方法传递参数过长的问题。这些技术将在接下来的文章里讲述。

0 0
原创粉丝点击