【问题标题】:Passing single object vs. passing multiple parameters传递单个对象与传递多个参数
【发布时间】:2016-04-28 03:55:18
【问题描述】:

假设我有以下内容

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的不必要的依赖


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

【问题讨论】:

  • 话虽如此,方法B

标签: design-patterns parameters method-call parameter-object


【解决方案1】:

方法A(裸参数)总是具有以下优点

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

方法 B (Parameter Object) 在以下情况下具有优势

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

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

所以,参数对象是

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

【讨论】:

  • 此类对象是否有任何普遍采用的名称,例如...DTO 或类似名称?
  • “参数对象”这个名字来自重构书。
  • @DaveSchweisguth 谁写了这本书?
  • @cikatomo Martin Fowler 是主要作者。本书:bookshop.org/books/…
【解决方案2】:

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 可以提供清晰性、减少重复、简化一致性等。

【讨论】:

  • 在本例中为 A 和 B 引入不同的 DTO。
  • 对 - 我想我要补充一点,等式的另一部分是软件开发人员在您的团队中的原则性如何,以及他们重构的意愿如何。在我的示例场景中,我建议制作新的 DTO,或者考虑将它们分开(因为它们仍然很小)。
【解决方案3】:

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

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

我经常使用的另一种方法是检查doStuff 方法所做的工作是否被分解为具有较少依赖性的更简单的方法。

我主要尝试遵循 Bob Martin 关于最多三个参数的建议。嗯,他居然说应该最多不超过一个! 任何增加都应该有正当理由。 参考这本好书:Clean Code

【讨论】:

    【解决方案4】:

    考虑一个具有 AddressCurrentInvoiceCustomer。哪个更正确-

    SendInvoiceToAddress(Invoice invoice, Address adress);
    

    SendInvoiceToAddress(Customer customer);
    

    我认为两者兼而有之。或者换一种说法——这真的取决于你的应用程序。

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

    如果您想将多张发票发送到多个地址(不一定是为了客户,出于任何目的),那是完全不同的故事。它可能也存在于 InvoiceSender 类(或类似的东西)中。它也与客户域无关。在这种情况下,客户只是一小部分正在发货的发票。另外值得注意的是,在这种情况下,您可能需要接口而不是具体的,因为客户的发票并说公司的发票可能是两个非常不同的类,它们恰好共享一个公共接口(在这种情况下为“几个属性”)。

    【讨论】:

    • 我认为这取决于命名。如果您已命名第二个,SendInvoiceToCustomer 传递客户类型将有意义
    • 你可以称它为 SendInvoice(customer) 或 SendInvoiceToAddress(customer) 或 SendInvoiceToCustomer(customer) 或 SendCurrentInvoceTo(customer)。它们都有意义。就像在现实生活中一样——在公司里更有意义的是——让一个人为每个人收集和发送所有邮件(“服务”),还是每个人分别发送自己的邮件?我认为两者都有道理。同步(“快来收我的邮件并立即发送!”)还是异步(“我们在外出邮箱中给您留下了十几个邮件信封,请在您有空的时候发送”)?他们都说得通。
    • 在这种情况下,发票和地址是:1)只有 2 个参数 2)都是对象。 3)他们不是客户的一部分,所以无论如何发送客户都是完全错误的正确的例子是:A)SendInvoiceToAddress(发票发票,地址地址); - 发送一个对象。或 B) SendInvoiceToAddress(invoice.title, invoice.amount, address.city, address.street, address.zip...); A - 正确,B - 不正确。
    猜你喜欢
    • 1970-01-01
    • 2012-11-20
    • 1970-01-01
    • 2010-09-07
    • 2021-01-23
    • 1970-01-01
    • 2013-07-08
    • 2021-09-21
    相关资源
    最近更新 更多