2010-03-12 154 views
69

有时,我们必须编写收到很多很多争论的方法,例如:将许多参数传递给方法的最佳实践?

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封装到地图中完全是浪费效率。 好的是,干净的签名,易于添加其他参数,修改最少。 这类问题的最佳做法是什么?

回答

101

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

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

  • 突破该方法分为多个方法,每个方法只需要参数的一个子集
  • 创建帮助类来保存参数组(通常是静态成员类)
  • 修改Builder对象构造模式方法调用。

欲了解更多详情,我鼓励您购买这本书,这是非常值得的。

+0

什么是“过长的参数”?我们何时可以说方法有太多参数?是否有特定的数字或范围? – 2017-01-14 22:39:39

+0

@RedM我一直认为超过3或4个参数是“过长” – jtate 2017-06-22 13:34:07

+1

@jtate是个人选择还是你正在关注官方文档? – 2017-06-22 14:57:34

2

好的做法是重构。这些对象意味着它们应该被传递给这个方法?它们是否应该封装成一个单一的对象?

+0

是的,他们应该。例如,一个大型的搜索表单,有许多不相关的约束和需要分页。你需要传递currentPageNumber,searchCriteria,pageSize ... – Sawyer 2010-03-12 12:00:07

4

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

+0

我不认为有必要创建一个类来保存方法参数。 – Sawyer 2010-03-12 11:56:58

+0

如果有多个传递相同参数的实例,我只会创建这个类。这将表明参数是相关的,可能无论如何都属于这些参数。如果你正在为一种方法创建一个类,治疗可能比疾病更糟糕。 – tvanfosson 2010-03-12 12:00:50

+0

是的 - 您可以将相关的参数移动到DTO或值对象中。是否有多个参数可选,即主方法是否被这些附加参数重载?在这种情况下 - 我觉得这是可以接受的。 – JoseK 2010-03-12 12:01:45

61

使用神奇字符串键地图是一个坏主意。你会失去任何编译时间检查,并且确实不清楚所需的参数是什么。你需要编写非常完整的文档来弥补它。你会记得几个星期之内那些字符串没有看代码吗?如果你犯了一个错字呢?使用错误的类型?直到您运行代码时才会发现。

改为使用模型。创建一个类,它将成为所有这些参数的容器。这样你就保持了Java的类型安全。您也可以围绕传递对象等方法,把它放在收藏等

当然,如果设定的参数是不是在其他地方使用或通过周围,有专门的模型可能是矫枉过正。有一个平衡点,所以使用常识。

2

使用地图是清洁调用签名一个简单的方法,但那么你有另外一个问题。您需要查看方法的主体,以查看该方法在该Map中所期望的内容,关键名称或值的类型。

一个清洁的方式将是组中的对象豆的所有参数,但仍不能完全解决问题。

你在这里是一个设计问题。对于一个方法,如果有超过7个参数,你就会开始有问题记住它们代表的是什么以及它们的顺序。从这里你将通过错误的参数顺序调用该方法来获得大量的错误。

您需要的应​​用程序不是一个最佳实践的一个更好的设计来发送大量的参数。

5

有一种称为Parameter object模式。

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

10

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

12

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

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

即使你没有发现自己传递相同的参数列表中,这样的时候,那么容易重构仍然会提高你的代码的可读性,这是总是好。如果您在3个月后查看自己的代码,那么当您需要修复错误或添加功能时,将更容易理解。

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

+0

垃圾回收会成为问题吗? – rupinderjeet 2016-12-07 04:00:04

+0

如果您在调用方函数中使参数对象为本地作用域,并且您没有对其进行变异,则不适用。在这种情况下,它很可能会被收集起来并且很快就会重用内存。 – dimitarvp 2016-12-07 04:05:33

+0

Imo,你应该也有'XXXParameter param = new XXXParameter();'可用,然后使用'XXXParameter.setObjA(objA)'; etc ... – satibel 2016-12-23 10:25:06

1

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

0

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

21

如果有许多可选参数,你可以创建流畅的API:

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

+1对于颇有创意的想法,我不得不承认! – 2010-12-19 14:14:09

+1

如果每个参数都是必需的,不是可选的? – Emerald214 2011-08-12 16:04:48

+1

您也可以使用这种方式的默认参数。另外,[builder pattern](http://en.wikipedia.org/wiki/Builder_pattern)与流畅的界面相关。我认为这应该是真正的答案。除了将长构造器分解为可选的更小的初始化方法之外。 – 2012-05-04 19:04:30

0
  • :与方法

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

    使用静态导入,您可以创建内部流畅的API链取代单一的方法

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

  • 使用地图会使您的方法易受攻击。如果有人使用你的方法拼写错了一个参数名称,或者在你的方法需要一个UDT的地方张贴一个字符串?

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

3

代码完成*建议两件事情:

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

*第一版,我知道我应该更新。另外,从OOP开始变得更受欢迎时编写第二版开始,这些建议可能会发生一些变化。

5

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

在这种情况下,使用构建器对象模式,如果您有大量参数并且并不总是需要所有这些参数,

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

它也增加了很多可读性。

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

+0

如果你的许多领域都是“最终”,你会推荐什么?这是让我无法编写帮助函数的主要原因。我想我可以将这些字段设置为私有的,并确保我不会在该类的代码中错误地修改它们,但我希望更优雅。 – ragerdl 2017-04-05 18:37:20

相关问题