传递单个对象与传递多个参数

Passing single object vs. passing multiple parameters

假设我有以下

Class A {
    Foo getFoo();
    Bar getBar();
    Baz getBaz();
}

我需要定义一个函数 doStuff,它使用 FooBarBazone 对象和做一些事情

我正在纠结哪种实现 doStuff 的方法更好(假设将 doStuff 放在 class A 中是不可取的)

方法一

void doStuff(Foo foo, Bar bar, Baz baz)
{ 
    //some operation
}

方法B

void doStuff(A a)
{
    Foo foo = a.getFoo();
    Bar bar = a.getBar();
    Baz baz = a.getBaz();
    //some operation
}

据我所知, (+优点,-缺点)

方法一

+清楚doStuff()作用于

的参数

-容易受到长参数列表的影响,更容易出现用户错误

方法B

+简单易用的方法

+似乎更具可扩展性 (?)

-对 class A

造成不必要的依赖

任何人都可以就这两种方法的优缺点分享更多见解吗?

方法A(裸参数)始终具有

的优点
  • 它要求方法作者输入更少,因为他们不必实现参数对象,
  • 它要求方法调用者键入更少,因为他们不必实例化参数对象
  • 它性能更好,因为不需要构造参数对象和垃圾收集
  • reader 可以单独从方法签名中看出各个参数是什么(但这是一把双刃剑;见下文)

方法 B (Parameter Object) 在

时具有优势
  • 参数作为一个组具有域含义,因此可以为参数对象指定一个名称来解释该含义,从而使 reader 不必阅读和理解组中的每个成员以及它们之间的关系
  • 参数列表在多个方法中使用,因此在每个方法中使用参数对象减少重复
  • 参数列表中的值作为一个组在多个方法之间传递,当它们可以作为单个参数对象传递时更容易
  • 某些值的组合无效;参数对象可以防止这些组合
  • 有些值是可选的,可以由参数对象提供,而不是(取决于您的语言)默认参数值或重载方法
  • 同一类型的参数不止一个,更容易发生值交换错误(尽管在这种情况下参数对象并不是更好,如果它 有一个与方法参数列表相同的构造函数)

参数对象引入了调用者和被调用者所依赖的新依赖关系,这并不是什么坏处,因为它是一个简单的 class,没有自己的依赖关系。

所以,参数对象是

  • 对于单个参数几乎不值得,有时对于双参数方法值得(例如点通常比 x、y 更好),有时不值得,并且对三个和更多参数的帮助越来越大
  • 当更多方法使用相同的参数列表时,帮助会越来越大

Parameter Objects 确实提供了一种很好的方法来封装 related 参数,以减少任何方法或构造函数的参数总数。 应该非常小心地确保参数对象确实包含真正相关的参数。

实际上,根据您所处理的 parameter types,有多种方法可以解决此问题。 如果您处理的参数是一般类型的参数,例如多个 Strings 或 Ints 并且客户端有可能实际传递错误的参数序列,这通常更有意义创建 custom types 即。使用可能的值创建 enum。这可以为您的参数提供良好的编译时间检查。 它们的另一个很好的用途是您可以将它们用于 return 函数中的复杂值。参见 here

我经常采用的另一种方法是检查并查看 doStuff 方法完成的工作是否可以分解为具有较少依赖性的更简单的方法。

原则上,我尝试遵循 Bob Martin 关于最多三个参数的建议。其实他说的嘛应该多半不会超过一个吧! 任何增加都应该有正当理由。 参考这本优秀的书:Clean Code

David 和 Som 的回答都有重要的信息需要考虑。我将添加以下内容:

与许多设计模式一样,做什么的决定取决于选项之间的连续统一体,这些选项各有优缺点。并不总是有一个正确的答案——更多的时候它取决于你想要享受哪些优点以及你愿意冒哪些缺点。

根据我的经验,当您有 相关 值且 总是 一起移动时,转移到 DTO 会很有帮助。大卫很好地描述了这种方法的优点。我看到这种方法的另一个缺点是,当 DTO 增长时,您可能会冒着向方法添加不必要的依赖项的风险。

例如,方法 A、B、C 和 D 接受 Foo、Bar 和 Baz,因此最好将这些参数组合到一个 DTO 中。那么方法 A 和 B 需要接受 Quux——你是否将 Quux 添加到 DTO 中强制 C 和 D 接受未使用的依赖项?当您测试 C 和 D 时,您为 Quux 传递什么值?当新开发人员使用方法 C 和 D 时,Quux 的存在是否会产生混淆?比较方法A和C时,是否清楚Quux应该或不应该如何定义?

当您最初需要所有方法的 Foo、Bar 和 Baz,但后来某些方法不再需要这些值时,会出现类似的情况。

我观察到这样一种经历:一个团队将 DTO 传递给另一个团队的服务,并努力正确填充和同步该 DTO 中的信息,而实际上真正需要的只是一个可以具有的单一值平凡地通过了。

除非这些值始终一致,否则您可能会产生混淆、增加测试负担和额外的开发工作。如果这些值总是在一起,DTO 可以提供清晰度、减少重复、简化一致性等。

考虑一个 Customer,它有一个 Address 和一个 CurrentInvoice。哪个更正确-

SendInvoiceToAddress(Invoice invoice, Address adress);

SendInvoiceToAddress(Customer customer);

我觉得两者都有。或者换句话说——这真的取决于你的应用程序。

如果每张发票(根据定义)属于一个客户,那是一回事。这意味着您的方法存在于 CustomerInvoiceSender class(或类似的东西)中。这完全属于客户领域。每个月您都希望发送该月的发票(仅此而已)。

如果您想将多张发票发送到多个地址(不一定是为了客户,出于任何目的),那就完全不同了。它也可能存在于 InvoiceSender class(或类似的东西)中。它也与客户域无关。在这种情况下,客户只是发货发票的一小部分。同样值得注意的是,在这种情况下,您可能需要接口而不是具体的,因为客户的发票和公司的发票可能是两个截然不同的 classes,它们恰好共享一个公共接口("a couple of properties" in这个案例)。