Java方法参数太多怎么办—Part5—方法命名

在前面文章(“Java方法参数太多怎么办”系列之四)中,讨论了如何使用方法重载减少方法和构造函数的参数,指出了这种方式的一些不足并建议使用多个不同的函数名代替重载。本文将深入讨论如何通过函数命名解决参数过多的问题,并且可以弥补方法重载的一些不足。

从减少参数的角度来看,方法重载的核心问题在于:当参数过多时,相同名字的方法到底可以重载多少次?当其中一些参数的类型相同时尤其如此。举个例子,我定义一个包含三个String属性的类,想通过三个构造函数分别初始化不同的属性。这样完全没法用重载来解决这个问题。通过试验发现,只有查阅注释(Javadoc)才能确定构造函数到底初始化了哪个String属性。不使用构造函数重载,通过定义不同的方法名能提高代码的可读性。

下面的示例代码展示了如何调用其它类的方法实例化Person。这些方法都有着长长的名字,详细描述了所需的参数。这意味着需要的方法注释更少,对于调用方法的开发者代码更具可读性,相比方法重载支持的参数组合方式更多也更具前景。

通过函数名描述Person实例化示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
publicPerson createPersonWithFirstAndLastNameOnly(finalString firstName,finalString lastName)
  {
  // implementation goes here ...
  }
 
publicPerson createEmployedHomeOwningFemale(finalFullName name,finalAddress address)
  {
  // implementation goes here ...
  }
 
publicPerson createEmployedHomeOwningMale(finalFullName name,finalAddress address)
  {
  // implementation goes here ...
  }
 
publicPerson createUnemployedHomeOwningFemale(finalFullName name,finalAddress address)
  {
  // implementation goes here ...
  }
 
publicPerson createEmployedRentingMale(finalFullName name,finalAddress address)
  {
  // implementation goes here ...
  }

上面的示例代码中,使用较长的描述性方法名可以让开发者更好地了解调用方法所需的参数。当然,我可以写更多的这样方法来覆盖不同的参数排列组合,这里只是列出其中的一个小集合。请注意我在示例代码中使用了参数对象(在之前的文章中定义过的Fu、lName和Address)来进一步减少调用方法时所需的参数。

上面的示例代码展示了在实例化过程中,通过不同的描述性方法名来显示哪些参数需要传递。在一些情况下,哪些参数可以由方法名知道无需传递。新手可能会觉得这种方法无法用于对象实例化和初始化,理由是Java中类的构造函数必须与类同名相同。这意味着构造函数仅能通过同名函数重载。幸运的是,Josh Bloch在每一版Effective Java的第一章都会解释这个问题。按照Bloch的说法,我们可以使用静态初始化工厂实例化类。这样做的好处之一是,我们可以用任意合理的方式命名方法。

下列的代码为我们展示了静态初始化工厂的功能。当我实现这些功能时,我喜欢定义一个或几个只提供静态初始化工厂调用的私有(标记为private,不能被其它类调用)构造函数。只有我定义的类必须使用这样的方式初始化,其他人使用静态初始化工厂初始化会更加简单。这样把参数很多的构造函数隐藏了起来,可以让这些构造函数的声明完全满足需求。具体地说,如果构造函数要求参数传入空值,可以通过定义不同的静态初始化工厂方法解决。这样调用者不必特意为这些参数传递入空值,而是由静态初始化工厂方法代替客户为构造函数传递空值。简而言之,静态初始化工厂方法为用户呈现了一个更整洁、更友善的接口并且隐藏了类定义中的带有过多参数构造函数。由于构造函数无法自定义方法名,无法直接使用重构解决参数过多的问题。如果需要,静态初始化工厂方法可以接受“原始”类型参数并在内部把它转为通用类型和参数对象,这是静态初始化工厂方法的另一个优势。以上所说的功能在下面的示例代码中一一列举了出来:

静态初始化工厂验证

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
62
63
/**
  * 构造函数标记为private,因为只有内部构造器会调用它创建实例。
*
  * @param newName Name of this person.
  * @param newAddress Address of this person.
  * @param newGender Gender of this person.
  * @param newEmployment Employment status of this person.
  * @param newHomeOwner Home ownership status of this person.
  */
  privatePerson(
  finalFullName newName,finalAddress newAddress,
  finalGender newGender,finalEmploymentStatus newEmployment,
  finalHomeownerStatus newHomeOwner)
  {
  this .name = newName;
  this .address = newAddress;
  this .gender = newGender;
  this .employment = newEmployment;
  this .homeOwnerStatus = newHomeOwner;
}
 
publicstaticPerson createInstanceWithNameAndAddressOnly(
  finalFullName newName,finalAddress newAddress)
  {
  returnnewPerson(newName, newAddress, null , null , null );
  }
 
publicstaticPerson createEmployedHomeOwningFemale(
  finalFullName newName,finalAddress newAddress)
  {
  returnnewPerson(
  newName, newAddress, Gender.FEMALE, EmploymentStatus.EMPLOYED, HomeownerStatus.HOME_OWNER);
  }
 
publicstaticPerson createEmployedHomeowningMale(
  finalFullName newName,finalAddress newAddress)
  {
  returnnewPerson(
  newName, newAddress, Gender.MALE, EmploymentStatus.EMPLOYED, HomeownerStatus.HOME_OWNER);
  }
 
publicstaticPerson createUnemployedMaleRenter(
  finalFullName newName,finalAddress newAddress)
  {
  returnnewPerson(
  newName, newAddress, Gender.MALE, EmploymentStatus.NOT_EMPLOYED, HomeownerStatus.RENTER);
  }
 
publicstaticPerson createPersonWithFirstNameLastNameAndAddress(
  finalName newFirstName,finalName newLastName,finalAddress newAddress)
  {
  returnnewPerson(
  newFullName.FullNameBuilder(newLastName, newFirstName).createFullName(),
  newAddress, null , null , null );
  }
 
publicstaticPerson createPersonWithFirstNameLastNameAndAddress(
  finalString newFirstName,finalString newLastName,finalAddress newAddress)
  {
  returnnewPerson(
  newFullName.FullNameBuilder(newName(newLastName),newName(newFirstName)).createFullName(),
  newAddress, null , null , null );
  }

如上面的示例代码所示,这些方法具有较高的可读性并且不会要求传入很多参数。最后两个示例结合了方法重载和静态初始化工厂。

 方法命名的好处和优点

相比简单的方法重载,恰如其分地定义描述了所需参数信息的方法名有一些优点。方法名可以根据方法的预期和假设定制,调用方法函数的代码的意图也更加明显。就像上面的示例代码那样,通过方法名可以看出哪些参数不必直接提供,因为它们被设定为方法的一部分(这种意图通过方法名而不是来注释来传达)。

有一个优势在本文并没有明确地说明:相比方法重载,方法名可以包含参数的单位或者其它背景信息。举个例子,可以用接收整数的方法setWholeLengthInMeters(int)和接收小数的方法setFractionalLengthInFeet(double)来代替需要同时接收整数和小数的方法setLength()。

 方法命名的代价和缺点

虽然使用不同名字的方法实例化和静态初始化工厂相比方法重载具有一些明显的优势,但不幸的是方法命名也具有方法重载的一些问题。一个相同的问题是,为了支持可能会用到的参数所有排列组合,需要编写大量的方法。拿上面的例子来说,为包含性别、房屋所有权和工作状况的所有组合方式就需要8个方法(2的3次方)。假设任意一个单独的参数值有2种以上可能,那么为了覆盖参数值的不同组合方法名数量就需要随着参数可能的数量增长。当然,参数值具有无限可能的情况不能用不同的方法名定义所有的可能,只能传递它的值而不是在方法中设定。

虽然描述性的方法名非常易于理解,但是用户在调用方法时可能会需要在长长的方法列表中跋涉。因此,方法命名可能导致由于方法过多而降低整体的可读性。另外,一些人可能不喜欢长长的方法名所据过多的屏幕空间。我个人并不介意长函数名,我认为它们为提高可读性在屏幕上显示额外的文本是值得的。有IDE和语言规范的帮助,基本不会在输入长方法名时出错。为开发者配备多个大屏监控器也使得长方法名的不是那么让人烦恼。

结论

方法名可以用来向用户传递重要信息。在努力净化特定方法的参数个数(包括减少参数个数)时,通过适当的方法命名可以了解方法的默认设置、哪些参数是不需要的,还可以解释其它参数的排列顺序及其特征。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值