将许多参数传递给方法的最佳实践?
Posted
技术标签:
【中文标题】将许多参数传递给方法的最佳实践?【英文标题】:Best practice for passing many arguments to method? 【发布时间】:2011-01-26 19:24:27 【问题描述】:有时,我们必须编写接收许多参数的方法,例如:
public void doSomething(Object objA , Object objectB ,Date date1 ,Date date2 ,String str1 ,String str2 )
遇到这类问题时,我经常将参数封装到一个map中。
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");
......
这不是一个好的做法,将参数封装到地图中完全是浪费效率。 好消息是,干净的签名,易于添加其他参数,修改最少。 这类问题的最佳做法是什么?
【问题讨论】:
【参考方案1】:在Effective Java,第 7 章(方法)第 40 项(仔细设计方法签名)中,布洛赫写道:
有三种技术可以缩短过长的参数列表:
将方法分解为多个方法,每个方法只需要参数的一个子集 创建辅助类来保存一组参数(通常是静态成员类) 将 Builder pattern 从对象构造调整为方法调用。更多详情,我鼓励你买这本书,真的很值得。
【讨论】:
@RedM 我一直认为超过 3 或 4 个参数是“过长” @jtate 是个人选择还是您在关注官方文档? @RedM 个人喜好:) @RedM 根据 Rober Martin 在他的 Clean Code 一书中的说法,方法应该是 Monadic(只有一个参数),因为多个参数很难理解。他还指出,如果您需要传递多个参数,最好创建一个包装类并传递该对象。 Effective Java 第三版,这是第 8 章(方法),第 51 项【参考方案2】:使用带有神奇字符串键的地图是个坏主意。您会丢失任何编译时间检查,而且还不清楚所需的参数是什么。您需要编写非常完整的文档来弥补它。几周后你会在不看代码的情况下记住那些字符串是什么吗?如果你打错了怎么办?使用错误的类型?在你运行代码之前你不会发现。
改为使用模型。创建一个类,它将成为所有这些参数的容器。这样你就可以保持 Java 的类型安全。您还可以将该对象传递给其他方法,将其放入集合等。
当然,如果参数集没有在其他地方使用或传递,则专用模型可能会过大。需要取得平衡,所以请使用常识。
【讨论】:
【参考方案3】:如果你有很多可选参数,你可以创建流畅的 API:用方法链替换单个方法
exportWithParams().datesBetween(date1,date2)
.format("xml")
.columns("id","name","phone")
.table("angry_robots")
.invoke();
使用静态导入,您可以创建内部流畅的 API:
... .datesBetween(from(date1).to(date2)) ...
【讨论】:
如果每个参数都是必需的,而不是可选的呢? 您也可以通过这种方式设置默认参数。此外,builder pattern 与流畅的接口有关。我认为这应该是真正的答案。除了将较长的构造函数分解为可选的较小的初始化方法。【参考方案4】:它被称为“引入参数对象”。如果您发现自己在多个地方传递了相同的参数列表,只需创建一个包含所有参数的类。
XXXParameter param = new XXXParameter(objA, objB, date1, date2, str1, str2);
// ...
doSomething(param);
即使您发现自己没有经常传递相同的参数列表,这种简单的重构仍然会提高您的代码可读性,这总是很好。如果您在 3 个月后查看您的代码,当您需要修复错误或添加功能时会更容易理解。
这当然是一个普遍的理念,由于你没有提供任何细节,我也不能给你更详细的建议。 :-)
【讨论】:
垃圾回收会不会有问题? 如果您在调用函数中将参数对象设为本地范围,并且不对其进行变异,则不会。在这种情况下,它很可能会被收集起来,并且它的内存会很快被重用。 Imo,您还应该有一个可用的XXXParameter param = new XXXParameter();
,然后使用XXXParameter.setObjA(objA)
;等等……【参考方案5】:
首先,我会尝试重构该方法。如果它使用这么多参数,它可能会太长。分解它既可以改进代码,也可以减少每个方法的参数数量。您还可以将整个操作重构为它自己的类。其次,我会寻找使用相同参数列表的相同(或超集)的其他实例。如果您有多个实例,则很可能表明这些属性属于一起。在这种情况下,创建一个类来保存参数并使用它。最后,我将评估参数的数量是否值得创建地图对象以提高代码可读性。我认为这是一个个人电话——这个解决方案的每一种方式都有痛苦,而且权衡点可能不同。对于六个参数,我可能不会这样做。 10 我可能会(如果没有其他方法首先起作用)。
【讨论】:
【参考方案6】:这在构造对象时经常会出现问题。
在这种情况下使用构建器对象模式,如果您有大量参数并且并不总是需要所有参数,它会很好地工作。
你也可以让它适应方法调用。
它还大大提高了可读性。
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
,你会推荐什么?这是使我无法编写辅助函数的主要因素。我想我可以将这些字段设为私有,并确保我不会在该类的代码中错误地修改它们,但我希望有更优雅的东西。【参考方案7】:
有一种模式称为Parameter object。 想法是使用一个对象代替所有参数。现在即使你以后需要添加参数,你只需要将它添加到对象中。方法接口保持不变。
【讨论】:
【参考方案8】:您可以创建一个类来保存该数据。虽然需要足够有意义,但比使用地图 (OMG) 要好得多。
【讨论】:
我认为没有必要创建一个类来保存方法参数。 如果有多个传递相同参数的实例,我只会创建类。这将表明参数是相关的并且可能无论如何都属于一起。如果您为单一方法创建一个类,则治疗可能比疾病更糟糕。 是的 - 您可以将相关参数移动到 DTO 或值对象中。是否有一些多个参数是可选的,即主方法被这些附加参数重载了?在这种情况下——我觉得这是可以接受的。 这就是我说的必须足够有意义。【参考方案9】:Code Complete* 提出了几点建议:
“将例程参数的数量限制为大约七。七是人们理解的神奇数字”(第 108 页)。 “按输入-修改-输出顺序放置参数...如果多个例程使用相似参数,请将相似参数按一致顺序放置”(第 105 页)。 将状态或错误变量放在最后。 正如tvanfosson 提到的,只传递例程需要的结构化变量(对象)的一部分。也就是说,如果您在函数中使用大部分结构化变量,则只需传递整个结构,但请注意这会在一定程度上促进耦合。* 第一版,我知道我应该更新。此外,自 OOP 开始变得更流行时编写第二版以来,其中一些建议可能已经改变。
【讨论】:
【参考方案10】:使用 Map 是清理调用签名的一种简单方法,但是您遇到了另一个问题。您需要查看方法的主体内部,以了解该方法在该 Map 中的预期内容、键名或值的类型。
更简洁的方法是将对象 bean 中的所有参数分组,但这仍然不能完全解决问题。
这里是一个设计问题。如果一个方法的参数超过 7 个,您将开始难以记住它们代表什么以及它们的顺序。从这里你会得到很多错误,只是以错误的参数顺序调用方法。
您需要更好的应用设计,而不是发送大量参数的最佳实践。
【讨论】:
【参考方案11】:良好的做法是重构。这些对象意味着它们应该被传递给这个方法吗?是否应该将它们封装到单个对象中?
【讨论】:
是的,他们应该这样做。例如,一个大型搜索表单,有许多不相关的约束和分页需求。你需要通过 currentPageNumber , searchCriteria , pageSize ...【参考方案12】:创建一个bean类,并设置所有参数(setter方法)并将这个bean对象传递给该方法。
【讨论】:
【参考方案13】:查看您的代码,看看为什么要传入所有这些参数。有时可以重构方法本身。
使用映射会使您的方法易受攻击。如果有人使用您的方法拼错了参数名称,或者在您的方法需要 UDT 的地方发布了字符串怎么办?
定义一个 Transfer Object 。它至少会为您提供类型检查;您甚至可以在使用点而不是在您的方法中执行一些验证。
【讨论】:
【参考方案14】:我会说坚持你以前的做法。 您的示例中的参数数量不多,但替代方案要可怕得多。
地图 - 你提到的效率问题,但这里更大的问题是:
来电者不知道要给您发送什么而不参考某些内容 否则...你有 javadocs,它准确地说明了哪些键和 使用值?如果你这样做(这很棒),那么有很多参数 也不是问题。 接受不同的参数类型变得非常困难。你 可以将输入参数限制为单一类型,或使用 Map包装器对象-这只是解决了问题,因为您首先需要填充包装器对象-而不是直接填充到您的方法,而是填充到参数对象的构造函数。 确定移动问题是否合适取决于所述对象的重用。例如:
不会使用它:它只会在第一次调用时使用一次,所以很多额外的代码来处理 1 行...?
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);
-
Builder 模式 - 在我看来,这是一种反模式。最理想的错误处理机制是更早检测,而不是更晚检测;但是使用构建器模式,缺少(程序员不认为包含它)强制参数的调用从编译时移动到运行时。当然,如果程序员故意将 null 或诸如此类的东西放在插槽中,那将是运行时,但仍然更早地捕获一些错误对于迎合拒绝查看他们正在调用的方法的参数名称的程序员来说是一个更大的优势。
我发现它只在处理大量 optional 参数时才合适,即使这样,好处充其量也是微不足道的。我非常反对建造者“模式”。
人们忘记考虑的另一件事是 IDE 在这一切中的作用。 当方法具有参数时,IDE 会为您生成大部分代码,并且您会用红线提醒您需要提供/设置什么。使用选项 3 时……你完全失去了这个。现在由程序员来做对了,在编码和编译时没有任何线索……程序员必须测试它才能发现。
此外,如果不必要地广泛采用选项 2 和 3,由于它会生成大量重复代码,在维护方面会产生长期的负面影响。代码越多,维护的越多,维护的时间和金钱就越多。
【讨论】:
【参考方案15】:这通常表明您的班级承担了多个职责(即您的班级做得太多)。
见The Single Responsibility Principle
了解更多详情。
【讨论】:
【参考方案16】:如果您传递的参数过多,请尝试重构该方法。也许它正在做很多它不应该做的事情。如果不是这种情况,请尝试用单个类替换参数。这样,您可以将所有内容封装在单个类实例中,并传递实例而不是参数。
【讨论】:
【参考方案17】:... Bob 是你的叔叔:用于创建对象的轻松花式 API!
https://projectlombok.org/features/Builder
【讨论】:
以上是关于将许多参数传递给方法的最佳实践?的主要内容,如果未能解决你的问题,请参考以下文章
将参数传递给从 R 中的字符串调用的用户定义函数的最佳方法是啥?
PHP MVC 最佳实践 - 将 Session 变量从控制器传递给模型类或直接在模型中访问
将文件x中指定的函数作为命令行参数传递给python中的文件y的最佳方法