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

时间:2010-03-12 11:51:09

标签: java performance parameters parameter-passing

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

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");
 ......
 }

这不是一个好习惯,将params封装到地图中完全是浪费效率。 好处是,干净的签名,容易添加其他params与最少的修改。 这种问题的最佳做法是什么?

16 个答案:

答案 0 :(得分:119)

Effective Java,第7章(方法),第40项(设计方法签名仔细)中,布洛赫写道:

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

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

有关详细信息,我鼓励您购买本书,这非常值得。

答案 1 :(得分:67)

使用带有神奇字符串键的地图是个坏主意。您丢失了任何编译时检查,并且实际上不清楚所需的参数是什么。您需要编写非常完整的文档来弥补它。你会记得几周内这些字符串是什么而不看代码吗?怎么打错了怎么办?使用错误的类型?在运行代码之前,您不会发现它。

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

当然,如果参数集没有在别处使用或传播,专用模型可能会过度。要达到平衡,所以要运用常识。

答案 2 :(得分:23)

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

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

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

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

答案 3 :(得分:12)

它被称为“引入参数对象”。如果您发现自己在几个地方传递相同的参数列表,只需创建一个包含所有参数列表的类。

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

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

这当然是一般哲学,既然你没有提供任何细节,我也不能给你更详细的建议。 : - )

答案 4 :(得分:10)

首先,我尝试重构该方法。如果它使用那么多参数,那么任何方式都可能太长。将其分解将改进代码并可能减少每个方法的参数数量。您也可以将整个操作重构为自己的类。其次,我会寻找其他实例,我使用相同参数列表的相同(或超集)。如果您有多个实例,则可能表示这些属性属于一起。在这种情况下,创建一个类来保存参数并使用它。最后,我将评估参数的数量是否值得创建一个地图对象以提高代码的可读性。我认为这是个人电话 - 这种解决方案各方面都有痛苦,而且权衡点可能会有所不同。对于六个参数,我可能不会这样做。对于10我可能会(如果没有其他方法首先工作)。

答案 5 :(得分:7)

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

在这种情况下使用构建器对象模式,如果您有大量参数并且并不总是需要所有参数,那么它可以正常工作。

您还可以将其调整为方法调用。

它还大大提高了可读性。

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()方法。

答案 6 :(得分:6)

有一种称为Parameter object的模式。

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

答案 7 :(得分:5)

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

答案 8 :(得分:4)

代码完成*提出了几点:

  • “将例程参数的数量限制为大约七。七是人们理解的神奇数字”(第108页)。
  • “将参数放入输入 - 修改 - 输出顺序...如果多个例程使用类似的参数,请按相同的顺序放置相似的参数”(第105页)。
  • 最后放置状态或错误变量。
  • 提到tvanfosson时,只传递例程所需的结构化变量(对象)部分。也就是说,如果你在函数中使用了大部分结构化变量,那么只需传递整个结构,但要注意这会在某种程度上促进耦合。

*第一版,我知道我应该更新。此外,自OOP开始变得更受欢迎以来,第二版的编写可能会有一些改变。

答案 9 :(得分:2)

良好的做法是重构。这些对象怎么样意味着它们应该被传递给这个方法?它们应该封装在一个对象中吗?

答案 10 :(得分:2)

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

更简洁的方法是将对象bean中的所有参数分组,但仍然无法完全解决问题。

这里有一个设计问题。如果一个方法有超过7个参数,您将开始遇到问题,记住它们代表的内容以及它们的顺序。从这里,您将通过以错误的参数顺序调用方法来获得大量错误。

您需要更好的应用程序设计,而不是发送大量参数的最佳做法。

答案 11 :(得分:1)

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

答案 12 :(得分:1)

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

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

    • 呼叫者在不提及某些内容的情况下不知道该给您发送什么邮件
      其他...您是否有javadocs会确切说明哪些键和
      使用值?如果您这样做(很棒),那么有很多参数 也不是问题。
    • 接受不同的参数类型变得非常困难。您 可以将输入参数限制为单一类型,也可以使用 Map 并强制转换所有值。两种选择都是 大部分时间都很恐怖。
  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或类似内容,那将是运行时,但仍能较早捕获一些错误,对于迎接拒绝查看所调用方法的参数名称的程序员来说,这是一个更大的优势。 我发现仅当处理大量 optional 参数时,它才是合适的,即使这样,其好处充其量也只是微不足道的。我非常反对构建者的“模式”。

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

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

答案 13 :(得分:0)

这通常表明你的班级承担不止一项责任(即你的班级太多了)。

请参阅The Single Responsibility Principle

了解更多详情。

答案 14 :(得分:0)

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

答案 15 :(得分:0)

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

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

  • 定义Transfer Object。它至少会为您提供类型检查;您甚至可以在使用时而不是在您的方法中执行某些验证。