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

43

假设我有以下内容

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

我需要定义一个函数doStuff,它使用一个对象的FooBarBaz进行某些操作。

我正苦恼于实现doStuff的哪种方法更好(假设将doStuff放在类A中是不可取的)。

方法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
}

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

方法A

+清晰明确doStuff()操作的参数

-容易受长参数列表以及用户错误的影响

方法B

+简单易用的方法

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

-创建了对类A的不必要依赖关系


是否有人能分享这两种方法的其他优缺点?


2
话虽如此,B方法 - JavaHopper
4个回答

53

方法A(原始参数)的优点包括:

  • 方法作者无需实现参数对象,因此需要输入的内容较少
  • 方法调用者无需创建参数对象,因此需要输入的内容也较少
  • 性能较好,因为无需构建和垃圾回收参数对象
  • 仅通过方法签名即可查看各个参数(但这是双刃剑;详见下文)

方法B(参数对象)的优点包括:

  • 如果参数作为一组具有领域含义,则可以为参数对象命名以解释其含义,避免读者阅读和理解每个成员及其关系
  • 如果参数列表在多个方法中使用,则使用参数对象会减少重复
  • 如果参数列表的值作为一组在多个方法之间传递,那么将它们作为单个参数对象传递更容易
  • 某些值的组合是无效的,参数对象可以防止这些组合
  • 某些值是可选的,可以由参数对象提供,而不是(根据您的语言)默认参数值或重载方法
  • 如果存在多个相同类型的参数,则更容易出现值交换错误(尽管如果参数对象具有与方法相同的参数列表构造函数,则参数对象在这种情况下并不更好)

参数对象引入新的依赖关系,调用者和被调用者都需要依赖它,但这并不是太大的缺点,因为它是一个没有自身依赖的简单类。

因此,参数对象:

  • 对于单个参数几乎从不值得使用;对于两个参数的方法有时候值得使用(例如,Point通常比x,y好),有时候不值得使用;对于三个及以上的参数,逐渐变得更加有帮助
  • 当更多方法使用相同的参数列表时,将更加有帮助

1
有没有一般被采用的名称来表示这样的对象,例如 ...DTO 或类似的东西? - skwisgaar
1
“Parameter Object”这个名称来自于《重构》一书。 - Dave Schweisguth
@DaveSchweisguth 谁写了这本书? - cikatomo
1
@cikatomo Martin Fowler 是主要作者。这本书:https://bookshop.org/books/refactoring-improving-the-design-of-existing-code/9780134757599 - Dave Schweisguth

8
参数对象提供了一种很好的方法来封装相关的参数,以减少任何方法或构造函数的参数总数。应该非常小心,确保参数对象实际包含真正相关的参数。
实际上,根据您处理的参数类型,有多种方法来解决此问题。如果您正在处理像一个以上的StringInt等通用类型的参数,并且客户端可能会传递错误的参数序列,则通常更有意义的做法是创建自定义类型,即创建具有可能值的枚举。这可以为您的参数提供良好的编译时检查。
它们的另一个好处是可以将它们用于从函数中返回复杂值。请参见此处
我经常采用的另一种方法是检查并确定doStuff方法完成的工作是否可以分解为具有较少依赖性的简单方法。
原则上,我尝试遵循Bob Martin最多三个参数的建议。他实际上说大多数情况下不应超过一个!任何增加都应该有理由。请参考这本优秀的书:Clean Code

3
大卫和Som的回答都提供了很好的信息,我想补充以下内容:
与许多设计模式一样,决定做什么取决于选项之间的利弊得失。并不总是有一个正确的答案 - 更多的时候,取决于你想享受哪些优点以及你愿意冒哪些风险。
根据我的经验,在你拥有相关值需要一直同时传递时,转换为DTO是有帮助的。大卫描述这种方法的优点很好。我看到这种方法的一个额外的缺点是,当DTO增长时,会增加方法之间的不必要依赖关系。
例如,方法A、B、C和D采用Foo、

在这个例子中,为A和B引入不同的DTO。 - RM.
没错 - 我想我会补充一下方程式的另一部分,那就是你团队中的软件开发人员有多有原则性,以及他们有多愿意重构。在我的示例场景中,我建议制作新的DTO,或者考虑将它们拆分(因为它们仍然相当小)。 - Gordon Bean

1
考虑一个拥有“地址”和“当前发票”的“客户”。哪个更正确 -
SendInvoiceToAddress(Invoice invoice, Address adress);

or

SendInvoiceToAddress(Customer customer);

我认为两者都可以。或者换句话说 - 这真的取决于你的应用程序。

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

如果您想向多个地址发送多个发票(不一定是针对客户,也可能是任何目的),那么这就是完全不同的故事。它可能还存在于InvoiceSender类中(或类似于此的类中)。这与客户领域无关。在这种情况下,客户只是发票被运送的一个小例子。还值得注意的是,在这种情况下,您可能需要接口而不是具体实现,因为客户的发票和公司的发票可能是两个非常不同的类,只是碰巧共享一个公共接口(在这种情况下是“一些属性”)。


1
我认为这取决于命名。如果您将第二个命名为“SendInvoiceToCustomer”,那么传递客户类型就有意义了。 - Emobe
您可以将其称为SendInvoice(customer)或SendInvoiceToAddress(customer)或SendInvoiceToCustomer(customer)或SendCurrentInvoceTo(customer),它们都是有意义的。就像在现实生活中的公司里一样,什么更有意义:让一个人为所有人收集和发送邮件(“服务”),还是让每个人单独发送自己的邮件?我认为两种方法都有意义。同步方式(“立即来收取我的邮件并发送!”)还是异步方式(“我们在寄出的邮箱中留了一打信封,请在方便时发送它们”)?它们都是有意义的。 - LongChalk
在这种情况下,发票和地址是: 1)只有2个参数 2)两者都是对象。 3)它们不是客户的一部分,因此在任何情况下都将客户发送是完全不正确的。正确的示例是: A)SendInvoiceToAddress(Invoice invoice,Address address); - 发送一个对象。 或者 B)SendInvoiceToAddress(invoice.title,invoice.amount,address.city,address.street,address.zip ...);A-是正确的,B-不是。 - Artem A

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接