设计问题:传递您使用的字段或传递对象?

时间:2009-07-07 14:21:21

标签: oop method-signature

我经常看到两种冲突的方法接口策略,概括如下:

// Form 1: Pass in an object.
double calculateTaxesOwed(TaxForm f) { ... }

// Form 2: Pass in the fields you'll use.
double calculateTaxesOwed(double taxRate, double income) { ... }

// use of form 1:
TaxForm f = ...
double payment = calculateTaxesOwed(f);

// use of form 2:
TaxForm f = ...
double payment = calculateTaxesOwed(f.getTaxRate(), f.getIncome());

我见过第二种形式的倡导者,特别是在动态语言中,可能更难评估正在使用的字段。

但是,我更喜欢第一种形式:它更短,错误的空间更小,如果对象的定义稍后更改,您不一定需要更新方法签名,也许只是改变您使用的方式方法内的对象。

这两种形式都有令人信服的一般情况吗?是否有明确的例子说明何时应该使用第二种形式?我可以指出是否有SOLID或其他OOP原则来证明我决定使用一种形式而不是另一种形式?如果您使用的是动态语言,上述任何答案都会改变吗?

12 个答案:

答案 0 :(得分:6)

说实话,这取决于所讨论的方法。

如果方法在没有对象的情况下有意义,那么第二种形式更容易重复使用并删除两个类之间的耦合。

如果方法依赖于对象,则公平地传递对象。

对于第三种形式,您可能会传递一个设计用于该方法的界面。通过第二种形式的灵活性,为您提供第一种形式的清晰度。

答案 1 :(得分:3)

这取决于你的方法的意图。

如果该方法旨在专门用于该对象并且仅对该对象有效,则传递该对象。它是一个很好的封装。

但是,如果该方法更通用,您可能希望单独传递参数。这样,当信息来自另一个源 (即不同类型的对象或其他派生数据)时,该方法更有可能被重用。

答案 2 :(得分:2)

我强烈建议第二种解决方案 - calculateTaxesOwed()计算一些数据,因此需要一些数字输入。该方法绝对与用户界面无关,而且反过来又不能将表单作为输入,因为您希望将业务逻辑与用户界面分开。

执行计算的方法应该(通常)甚至不属于与用户界面相同的模块。在这种情况下,您将获得循环依赖,因为用户界面需要业务逻辑,业务逻辑需要用户界面表单 - 这是一个非常强烈的迹象表明存在错误(但仍然可以使用基于接口的编程解决)。

<强>更新

如果纳税表不是用户界面表单,则情况会有所改变。在这种情况下,我建议使用GetOwedTaxes()类的实例方法OwedTaxes或实例属性TaxForm公开该值,但我不会使用静态方法。如果计算可以在别处重用,那么仍然可以创建一个使用值而不是表单的静态辅助方法,并从实例方法或属性中调用这个辅助方法。

答案 3 :(得分:1)

我认为这不重要。如果您传入Object,因为它可能会发生变异,您可以打开副作用。这可能是你想要的。为了缓解这种情况(并帮助测试),您可能更好地传递接口而不是具体类型。好处是,如果要访问Object的另一个字段,则无需更改方法签名。

传递所有参数可以更清楚地了解类型需要什么,并且可以使测试更容易(尽管如果使用接口,这不是一个好处)。但是你会有更多的重构。

根据其优点判断每种情况,并选择最不痛苦的。

答案 4 :(得分:1)

传递参数可以更容易进行单元测试,因为您不需要模拟完整数据的整个对象只是为了测试基本上只是静态计算的功能。如果只有两个字段被使用,对象很多,我只倾向于传递这些字段,其他条件相同。

那就是说,当你最终得到六个,七个或更多字段时,是时候考虑在“有效载荷”类中传递整个对象或字段的子集(或结构/字典,取决于语言的风格)。长方法签名通常令人困惑。

另一个选择是使它成为一个类方法,所以你不必传递任何东西。测试不太方便,但值得考虑的是,何时只能在TaxForm对象的数据上使用此方法。

答案 5 :(得分:1)

我意识到这很大程度上是所使用示例的工件,因此它可能不适用于许多实际情况,但是,如果函数与特定类绑定如此强烈,那么它不应该是:< / p>

double payment = f.calculateTaxesOwed;

对我来说,税务文件本身更适合计算相关税收,而不是将责任落在效用函数上,特别是考虑到不同的税收形式倾向于使用不同的税表或计算方法。

答案 6 :(得分:0)

第一种形式的一个优点是

  1. 抽象 - 编程到接口而不是实现。从长远来看,它可以使代码的维护更容易,因为只要TaxForm的界面没有改变,你就可以在不影响客户端代码的情况下改变TaxForm的实现。

答案 7 :(得分:0)

这与Martin Fowler关于重构的书中的“介绍参数对象”相同。如果存在一组倾向于一起传递的参数,Fowler建议您执行此重构。

答案 8 :(得分:0)

如果你相信得墨忒耳定律,那么你会赞成准确传递所需要的东西:

http://en.wikipedia.org/wiki/Law_of_Demeter

http://www.c2.com/cgi/wiki?LawOfDemeter

答案 9 :(得分:0)

要操纵的UI和数据的分离

在您的情况下,您缺少一个中间类,比如TaxInfo,代表要征税的实体。原因是UI(形式)和业务逻辑(如何计算税率)在两个不同的“变化轨道”上,一个随着演示技术而变化(“网络”,“网络2.0”,“WPF”,. ..),另一个用法律术语改变。在它们之间定义清晰的接口。


一般性讨论,使用示例:

考虑为名片创建位图的功能。是函数的目的

(1) //使用名字和姓氏格式化名片标题

OR

(2) //从Person记录

格式化businnes卡片名称

第一种选择更通用,耦合较弱,通常是可取的。但是,在许多情况下,对变更请求的可靠性较低 - 例如考虑“案例2017:将名片添加到名片”。

更改实现(添加person.Initial)通常比更改界面更容易,更快。

最终选择的是您期望的更改类型:是否更有可能需要来自Person记录的更多信息,或者您是否更有可能为其他数据结构创建名片标题而不是Person

如果那是“未定的”,那么你不能选择目的(1)或(2)我宁愿选择(2),因为语法清洁。

答案 10 :(得分:0)

如果我选择了其中一个,我总是选择第二个 - 如果你发现你(无论出于何种原因)需要计算欠税,但你没有{的实例怎么办? {1}}?

这是一个相当简单的例子,但是我已经看到一个方法,一个执行相对简单的任务的方法具有难以创建的复杂输入,使得该方法比应该使用的方法更难以使用。 (作者根本没有考虑过其他人可能想要使用那种方法!)

就个人而言,为了使代码更具可读性,我可能会有两个:

TaxForm

我的经验法则是尽可能使用一种方法来完全获取所需的输入 - 它非常容易编写包装器方法。

答案 11 :(得分:0)

就个人而言,我会选择#2,因为它更清楚该方法需要什么。通过TaxForm(如果它是我认为的那样,就像一个Windows窗体)有点臭,让我有点畏缩(&gt; _&lt;)。

我只在传递特定于计算的DTO时使用第一个变体,例如IncomeTaxCalculationInfo对象,它将包含TaxRate和Income以及计算方法中最终结果所需的任何其他内容,但绝不会像Windows / Web表单。