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

目录

  1. 自定义类型

本文是这个系列的第一篇文章,介绍了采用自定义类型处理参数过多的问题。如果你也希望参与类似的系列文章翻译,可以加入我们的Android开发技术翻译 小组。

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

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

使用自定义类型一方面可以减少构造函数和方法的传参个数,另一方面又可以增强参数列表的可读性并且降低参数位置放错的可能性。自定义类型的实现方式包括Data Transfer ObjectsJavaBeansValue ObjectsReference Objects或者其他(在Java中经典的实现方式:枚举)自定义类型。

下面来看一个例子,该方法包含多个String和boolean类型参数:

 /**
    * 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
   }

可以发现很容易搞混参数的顺序,然后把它们放错位置。我通常更乐意通过改变参数类型来做一些提高,以期减少参数个数。下面这些代码展示了如何使用自定义类型。

三个名字可以改为自定义类型Name,而不是使用String。

Name.java

package dustin.examples;

/**
 * Name representation.
 * 
 * @author Dustin
 */
public final class Name
{
   private final String name;

   public Name(final String newName)
   {
      this.name = newName;
   }

   public String getName()
   {
      return this.name;
   }

   @Override
   public String toString()
   {
      return this.name;
   }
}

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

Salutation.java

package dustin.examples;

/**
 * Salutations for individuals' names.
 * 
 * @author Dustin
 */
public enum Salutation
{
   DR,
   MADAM,
   MISS,
   MR,
   MRS,
   MS,
   SIR
}

Suffix.java

package dustin.examples;

/**
 * Suffix representation.
 * 
 * @author Dustin
 */
public enum Suffix
{
   III,
   IV,
   JR,
   SR
}

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

Gender.java

package dustin.examples;

/**
 * Gender representation.
 * 
 * @author Dustin
 */
public enum Gender
{
   FEMALE,
   MALE
}

EmploymentStatus.java

package dustin.examples;

/**
 * Representation of employment status.
 * 
 * @author Dustin
 */
public enum EmploymentStatus
{
   EMPLOYED,
   NOT_EMPLOYED
}

HomeOwnerStatus.java

package dustin.examples;

/**
 * Representation of homeowner status.
 * 
 * @author Dustin
 */
public enum HomeownerStatus
{
   HOME_OWNER,
   RENTER
}

此人的地址信息可以通过如下代码所示的自定义类型作为参数进行传递。
StreetAddress.java

package dustin.examples;

/**
 * Street Address representation.
 * 
 * @author Dustin
 */
public final class StreetAddress
{
   private final String address;

   public StreetAddress(final String newStreetAddress)
   {
      this.address = newStreetAddress;
   }

   public String getAddress()
   {
      return this.address;
   }

   @Override
   public String toString()
   {
      return this.address;
   }
}

City.java

package dustin.examples;

/**
 * City representation.
 * 
 * @author Dustin
 */
public final class City
{
   private final String cityName;

   public City(final String newCityName)
   {
      this.cityName = newCityName;
   }

   public String getCityName()
   {
      return this.cityName;
   }

   @Override
   public String toString()
   {
      return this.cityName;
   }
}

State.java

package dustin.examples;

/**
 * Simple representation of a state in the United States.
 * 
 * @author Dustin
 */
public enum 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
}

通过使用自定义类型,开始展示的方法的代码变得更加易读易懂,同时更不容易出错了。下面是使用自定义类型改写后的方法:

   public Person createPerson(
      final Name lastName,
      final Name firstName,
      final Name middleName,
      final Salutation salutation,
      final Suffix suffix,
      final StreetAddress address,
      final City city,
      final State state,
      final Gender gender,
      final EmploymentStatus employment,
      final HomeownerStatus homeowner)
   {
      // implementation goes here
   }

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

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


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

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

自定义类型的代价和缺点

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

结论

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

原文链接: dzone 翻译: ImportNew.com - 王村平
译文链接: http://www.importnew.com/6518.html
[ 转载请保留原文出处、译者和译文链接。]



相关文章

发表评论

Comment form

(*) 表示必填项

4 条评论

  1. CuGBabyBeaR 说道:

    既然都新建了一个Name类 为什么不让Name类自己带有 first middle last三个字段 直接只传递一个Name型的参数代替现在的3个参数呢?

    Thumb up 1 Thumb down 0

  2. 王村平 说道:

    你好!如果你把first middle last放在Name类当中,这与开始那种放置方式有什么区别呢?我们在传参的时候,还是很可能放错first middle last的顺序啊

    Thumb up 0 Thumb down 0

  3. 群虾 说道:

    可以方法内部多个对各种 name的check

    Thumb up 0 Thumb down 0

  4. Retina 说道:

    我觉得跟没优化前的参数长度差不多,只是吧编译检查尽可能交给编译器。

    Thumb up 0 Thumb down 0

跳到底部
返回顶部