将许多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封装成地图完全是浪费效率。 好的是,干净的签名,容易添加其他参数修改最less。 这种问题最好的做法是什么?
在Effective Java中 ,第7章(方法),第40项(仔细devise方法签名),Bloch写道:
缩短过长的参数列表有三种技术:
- 将该方法分解成多个方法,每个方法只需要参数的一个子集
- 创build帮助类来保存一组参数(通常是静态成员类)
- 将Builder模式从对象构造调整为方法调用。
更多的细节,我鼓励你买这本书,这是非常值得的。
使用具有神奇string键的地图是一个坏主意。 你会失去任何编译时间的检查,而且真正不清楚所需的参数是什么。 你需要编写非常完整的文档来弥补它。 你会记得几个星期之内那些string没有看代码吗? 如果你犯了一个错字呢? 使用错误的types? 你直到你运行代码才会发现。
而是使用一个模型。 做一个类将是所有这些参数的容器。 这样你保持Java的types安全。 您也可以将该对象传递给其他方法,将其放入集合等中
当然,如果这组参数没有在其他地方使用,或者传递过来,那么专用的模型可能是过度的。 有一个平衡点,所以使用常识。
如果您有许多可选参数,则可以创buildstream畅的API:将方法链replace为单一方法
exportWithParams().datesBetween(date1,date2) .format("xml") .columns("id","name","phone") .table("angry_robots") .invoke();
使用静态导入可以创build内部stream畅的API:
... .datesBetween(from(date1).to(date2)) ...
它被称为“引入参数对象”。 如果你发现自己在几个地方传递了相同的参数列表,只需创build一个包含它们的类。
XXXParameter param = new XXXParameter(objA, objB, date1, date2, str1, str2); // ... doSomething(param);
即使你没有经常发现自己传递相同的参数列表,那么简单的重构仍然会提高你的代码可读性,这总是很好的。 如果您在3个月后查看您的代码,那么在您需要修复错误或添加function时,将会更容易理解。
这当然是一个普遍的哲学,既然你没有提供任何细节,我也不能给你更详细的build议。 🙂
首先,我试图重构该方法。 如果使用的参数太多,可能太长。 将其分解将改善代码并可能减less每个方法的参数数量。 您可能也可以将整个操作重构为自己的类。 其次,我会查找其他实例,我使用相同的参数列表(或超集)。 如果您有多个实例,则可能表示这些属性属于一个整体。 在这种情况下,创build一个类来保存参数并使用它。 最后,我会评估参数的数量是否值得创build一个地图对象来提高代码的可读性。 我认为这是一个个人的呼声 – 这个解决scheme的每一个方面都是痛苦的,而且权衡点可能有所不同。 对于六个参数我可能不会这样做。 对于10我可能会(如果没有其他方法首先工作)。
有一个叫做Parameter对象的模式。
想法是用一个对象来代替所有的参数。 现在,即使您稍后需要添加参数,您只需将其添加到对象即可。 方法接口保持不变。
你可以创build一个类来保存这些数据。 需要有足够的意义,但要比使用地图(OMG)好得多。
构build对象时,这往往是个问题。
在这种情况下,使用builder对象模式 ,如果你有大的参数列表,并不总是需要所有的参数,那么它就可以工作。
您也可以将其调整为方法调用。
它也提高了可读性。
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();
您也可以将validation逻辑放入Builder中。()和build()方法。
代码完成*提示了一些事情:
- “把例程的参数数量限制在七个左右,七是人们理解的幻数”(p108)。
- “将参数放入input – 修改 – 输出顺序…如果几个例程使用相似的参数,请按照一致的顺序放置相似的参数”(第105页)。
- 最后放置状态或错误variables。
- 正如tvanfosson所提到的,只传递例程需要的结构化variables(对象)的部分。 也就是说,如果你正在使用函数中的大部分结构化variables,那么只需要传递整个结构,但要注意,这在一定程度上促进了耦合。
*第一版,我知道我应该更新。 另外,从OOP开始变得越来越stream行的时候开始写第二版,这个build议可能会有些变化。
好的做法是重构。 这些对象意味着它们应该被传递给这个方法呢? 它们是否应该被封装成一个单一的对象?
使用地图是一个简单的方法来清理呼叫签名,但你有另一个问题。 您需要查看方法体内部,以查看该方法在该Map中所期望的内容,键名称或值的types。
一个更简洁的方法是将对象bean中的所有参数进行分组,但仍然不能完全解决问题。
你在这里有一个devise问题。 对于一个方法,如果有7个以上的参数,你就会开始记住他们所代表的东西以及他们的顺序。 从这里你将会以错误的参数顺序调用方法来获取大量的错误。
您需要更好的应用程序devise,而不是发送大量参数的最佳实践。
创build一个bean类,并设置所有参数(setter方法),并将这个bean对象传递给方法。
这往往表明你的class级有超过一个责任(即你的class级做得太多)。
见单一责任原则
了解更多详情。
如果你传递了太多的参数,然后尝试重构该方法。 也许它正在做很多事情,这是不应该做的。 如果不是这种情况,请尝试用一个类代替参数。 通过这种方式,您可以将所有内容封装在一个类实例中,并传递实例而不是参数。
-
看看你的代码,看看为什么所有这些参数都被传入。有时可能会重构方法本身。
-
使用地图会使您的方法易受攻击。 如果有人使用你的方法拼错一个参数名称,或者在你的方法需要一个UDT的地方张贴一个string呢?
-
定义一个传输对象 。 它至less会为您提供types检查; 甚至有可能在你使用的地方进行一些validation,而不是在你的方法中。
这是一个古老的线程,但是,如果你有类似的参数,你可能会使用可变参数
就像是
public void doSomething(Object .. objects){}