将许多参数传递给方法的最佳实践?


90

有时,我们必须编写接收许多参数的方法,例如:

public void doSomething(Object objA , Object objectB ,Date date1 ,Date date2 ,String str1 ,String str2 )
{
}

遇到此类问题时,我经常将参数封装到映射中。

Map<Object,Object> params = new HashMap<Object,Object>();
params.put("objA",ObjA) ;

......

public void doSomething(Map<Object,Object> params)
{
 // extracting params 
 Object objA = (Object)params.get("objA");
 ......
 }

这不是一个好习惯,将参数封装到地图中完全是浪费效率。好的是,干净的签名,易于以最少的修改添加其他参数。解决此类问题的最佳实践是什么?

Answers:


137

在《有效的Java》第7章(方法)第40项(仔细设计方法签名)中,Bloch写道:

可以使用三种技术来缩短过长的参数列表:

  • 将方法分为多个方法,每个方法仅需要一部分参数
  • 创建帮助程序类来保存参数组(通常是静态成员类)
  • 使Builder模式从对象构造适应方法调用。

有关更多详细信息,我建议您购买这本书,这确实值得。


什么是“过长参数”?我们什么时候可以说方法有太多参数?有特定的数字或范围吗?
Red M

2
@RedM我一直认为超过3或4个参数的值“太长”
jtate

1
@jtate是个人选择,还是您遵循官方文档?
红色M

1
@RedM个人喜好:)
jtate

2
在有效Java的第三版中,这是第8章(方法),项目51
GarethOwen

71

使用带有魔术性String键的映射是一个坏主意。您会失去所有的编译时间检查,而且还不清楚所需的参数是什么。您需要编写非常完整的文档来弥补它。您会在几周后不看代码就记得那些字符串是什么吗?如果您打错了怎么办?使用了错误的类型?在运行代码之前,您不会找到答案。

而是使用模型。创建一个将成为所有这些参数的容器的类。这样,您就可以保持Java的类型安全。您还可以将该对象传递给其他方法,将其放入集合等。

当然,如果未在其他地方使用或传递参数集,则专用模型可能会过大。需要取得平衡,因此请使用常识。


24

如果您有许多可选参数,则可以创建流利的API:用方法链替换单个方法

exportWithParams().datesBetween(date1,date2)
                  .format("xml")
                  .columns("id","name","phone")
                  .table("angry_robots")
                  .invoke();

使用静态导入,您可以创建内部流畅的API:

... .datesBetween(from(date1).to(date2)) ...

2
如果每个参数都是必需参数而不是可选参数,该怎么办?
emeraldhieu

1
您也可以通过这种方式使用默认参数。同样,构建器模式与流畅的界面有关。我认为这应该是答案。除了将长构造函数分解为较小的可选初始化方法之外,其他方法也是如此。
Ehtesh Choudhury

13

它称为“介绍参数对象”。如果发现自己在多个地方传递了相同的参数列表,则只需创建一个包含所有参数的类即可。

XXXParameter param = new XXXParameter(objA, objB, date1, date2, str1, str2);
// ...
doSomething(param);

即使您发现自己没有经常传递相同的参数列表,这种简单的重构仍然可以提高代码的可读性,这总是很好的。如果三个月后查看代码,则在需要修复错误或添加功能时会更容易理解。

当然,这是一种普遍的哲学,并且由于您没有提供任何详细信息,因此我也无法为您提供更详细的建议。:-)


垃圾收集会成为问题吗?
rupinderjeet '16

如果您在调用程序函数中将参数对象设置为局部范围,并且不进行更改,则不会。在这种情况下,很可能会对其进行收集,并很快重新使用其内存。
dimitarvp

Imo,您还应该有一个XXXParameter param = new XXXParameter();可用的,然后使用XXXParameter.setObjA(objA);等等...
satibel

10

首先,我将尝试重构该方法。如果使用了那么多参数,那么它可能太长了。分解它既可以改善代码,又可以减少每种方法的参数数量。您也许还可以将整个操作重构为自己的类。其次,我会寻找其他使用相同参数列表(或超集)的实例。如果您有多个实例,则可能表明这些属性属于同一实例。在这种情况下,请创建一个类来保存参数并使用它。最后,我将评估参数的数量是否值得创建一个映射对象以提高代码的可读性。我认为这是个私人电话,此解决方案会给每个人带来痛苦,并且折衷点可能会​​有所不同。对于六个参数,我可能不会这样做。我可能会花10个时间(如果没有其他方法先行)。


8

在构造对象时,这通常是一个问题。

在这种情况下,请使用构建器对象模式,如果您有大量的参数列表并且并不总是需要所有参数,那么它会很好用。

您还可以使其适应方法调用。

它还大大提高了可读性。

public class BigObject
{
  // public getters
  // private setters

  public static class Buider
  {
     private A f1;
     private B f2;
     private C f3;
     private D f4;
     private E f5;

     public Buider setField1(A f1) { this.f1 = f1; return this; }
     public Buider setField2(B f2) { this.f2 = f2; return this; }
     public Buider setField3(C f3) { this.f3 = f3; return this; }
     public Buider setField4(D f4) { this.f4 = f4; return this; }
     public Buider setField5(E f5) { this.f5 = f5; return this; }

    public BigObject build()
    {
      BigObject result = new BigObject();
      result.setField1(f1);
      result.setField2(f2);
      result.setField3(f3);
      result.setField4(f4);
      result.setField5(f5);
      return result;
    }
  }
}

// Usage:
BigObject boo = new BigObject.Builder()
  .setField1(/* whatever */)
  .setField2(/* whatever */)
  .setField3(/* whatever */)
  .setField4(/* whatever */)
  .setField5(/* whatever */)
  .build();

您还可以将验证逻辑放入Builder set ..()和build()方法中。


如果您的许多领域是您会推荐什么final?这是使我无法编写帮助程序功能的主要内容。我想我可以将这些字段设为私有,并确保在该类的代码中不要对它们进行错误的修改,但是我希望有一些更优雅的方法。
ragerdl

7

有一个称为Parameter object的模式。

想法是使用一个对象代替所有参数。现在,即使以后需要添加参数,也只需将其添加到对象中。方法界面保持不变。


5

您可以创建一个类来保存该数据。尽管需要足够有意义,但是比使用地图(OMG)更好。


我认为没有必要创建一个类来保存方法参数。
索耶2010年

如果有多个传递相同参数的实例,我只会创建该类。这将表明这些参数是相关的,并且无论如何可能都属于同一参数。如果您要为单一方法创建类,则治愈可能比疾病还差。
tvanfosson 2010年

是的-您可以将相关参数移动到DTO或值对象中。多个参数中的某些参数是否可选,即main方法被这些附加参数所重载?在这种情况下-我认为这是可以接受的。
JoseK 2010年

我的意思是说这必须足够有意义。
约翰内斯·鲁道夫

4

代码完成*提出了以下几点建议:

  • “将例程的参数数限制为七个左右。七个是让人理解的神奇数字”(第108页)。
  • “按输入-修改-输出的顺序放置参数...如果多个例程使用相似的参数,则将相似的参数以一致的顺序放置”(p 105)。
  • 将状态或错误变量放在最后。
  • 正如tvanfosson所述,仅传递例程所需的结构化变量(对象)的部分。就是说,如果您在函数中使用大多数结构化变量,则只需传递整个结构,但要注意,这在某种程度上促进了耦合。

*第一版,我知道我应该更新。同样,自从第二版在OOP开始流行时开始编写以来,其中一些建议可能已经改变。


2

好的做法是重构。这些对象意味着将它们传递给此方法呢?是否应该将它们封装到单个对象中?


是的,他们应该。例如,大型搜索表单具有许多不相关的约束和分页需求。您需要传递currentPageNumber,searchCriteria,pageSize ...
Sawyer 2010年

2

使用映射是清除呼叫签名的一种简单方法,但是您还有另一个问题。您需要查看方法的主体以查看该方法在Map中的期望,键名是什么或值具有什么类型。

较干净的方法是将所有参数分组到一个对象bean中,但这仍然不能完全解决问题。

您在这里遇到的是设计问题。如果一个方法有7个以上的参数,则在记住它们代表什么和具有什么顺序时会遇到问题。从这里开始,仅以错误的参数顺序调用方法,您将获得许多错误。

您需要对应用程序进行更好的设计,而不是发送大量参数的最佳实践。


1

创建一个bean类,并设置所有参数(setter方法),然后将此bean对象传递给该方法。


1
  • 查看您的代码,看看为什么所有这些参数都被传入。有时可以重构方法本身。

  • 使用地图会使您的方法容易受到攻击。如果有人使用您的方法拼写了一个错误的参数名称,或在您的方法要求使用UDT的地方发布了字符串,该怎么办?

  • 定义一个传输对象。至少会为您提供类型检查;您甚至有可能在使用时而不是在您的方法内执行一些验证。



0

如果传递的参数过多,请尝试重构该方法。也许它正在做很多本不应该做的事情。如果不是这种情况,请尝试使用单个类替换参数。这样,您可以将所有内容封装在单个类实例中,并传递实例而不是参数。


0

我会说坚持以前的做法。您的示例中的参数数量并不多,但是替代方法更加可怕。

  1. 地图-您提到了效率问题,但是更大的问题是:

    • 调用者在不参考其他
      内容的情况下不知道该向您发送什么
      。如果这样做(很好),那么拥有很多参数也不是问题。
    • 接受不同的参数类型变得非常困难。您可以将输入参数限制为单个类型,也可以使用Map <String,Object>并强制转换所有值。在大多数情况下,这两种选择都是可怕的。
  2. 包装器对象-这只是一个问题,因为您首先需要填充包装器对象-而不是直接添加到您的方法中,而是对象参数的构造函数。确定移动问题是否适当取决于所述对象的重用。例如:

不会使用它:它将仅在第一次调用时使用一次,因此有很多其他代码可以处理一行...?

{
    AnObject h = obj.callMyMethod(a, b, c, d, e, f, g);
    SomeObject i = obj2.callAnotherMethod(a, b, c, h);
    FinalResult j = obj3.callAFinalMethod(c, e, f, h, i);
}

可以使用它:在这里,它可以做更多的事情。首先,它可以考虑3个方法调用的参数。它本身也可以执行其他2行...因此从某种意义上说,它成为状态变量...

{
    AnObject h = obj.callMyMethod(a, b, c, d, e, f, g);
    e = h.resultOfSomeTransformation();
    SomeObject i = obj2.callAnotherMethod(a, b, c, d, e, f, g);
    f = i.somethingElse();
    FinalResult j = obj3.callAFinalMethod(a, b, c, d, e, f, g, h, i);
}
  1. 构建器模式-我认为这是一种反模式。最理想的错误处理机制是尽早检测,而不是后期检测。但是在使用构建器模式时,缺少必需的调用(程序员不认为包含此调用)的强制性参数会从编译时移至运行时。当然,如果程序员有意在插槽中放入null或类似内容,那将是运行时,但仍能较早捕获一些错误,对于迎接拒绝查看所调用方法的参数名称的程序员来说,这是一个更大的优势。我发现它仅在处理大量可选参数时才适用,即使那样,其好处充其量也是微不足道的。我非常反对建造者的“模式”。

人们忘记考虑的另一件事是IDE在所有这些方面的作用。当方法具有参数时,IDE会为您生成大多数代码,并且红线提醒您需要提供/设置的内容。使用选项3时,您将完全失去这一点。现在由程序员来决定正确,并且在编码和编译期间没有任何提示……程序员必须对其进行测试以找出答案。

此外,如果选项2和3不必要地广泛采用,则由于其生成的大量重复代码而在维护方面具有长期负面影响。代码越多,维护的内容就越多,维护它所花费的时间和金钱也就越多。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.