12

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

// 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 原则我可以指出来证明我决定使用一种形式而不是另一种形式?如果您使用动态语言,上述任何答案是否会发生变化?

4

12 回答 12

6

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

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

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

对于第三种形式,您可能有一个很好的论据,您可以传递一个设计用于该方法的接口。为您提供第一种形式的清晰性和第二种形式的灵活性。

于 2009-07-07T14:30:49.643 回答
3

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

如果该方法设计为专门用于该对象且仅适用于该对象,则传递该对象。它提供了一个很好的封装。

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

于 2009-07-07T14:27:11.787 回答
2

我强烈推荐第二种解决方案 -calculateTaxesOwed()计算一些数据,因此需要一些数字输入。该方法与用户界面完全无关,并且不应该将表单用作输入,因为您希望将业务逻辑与用户界面分开。

执行计算的方法应该(通常)不属于与用户界面相同的模块。在这种情况下,您会得到循环依赖,因为用户界面需要业务逻辑,而业务逻辑需要用户界面形式——这是一个非常强烈的指示,表明出现了问题(但仍然可以使用基于接口的编程来解决)。

更新

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

于 2009-07-07T14:30:45.183 回答
1

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

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

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

于 2009-07-07T14:27:36.873 回答
1

只传递参数可以更容易进行单元测试,因为您不需要模拟充满数据的整个对象来测试本质上只是静态计算的功能。如果只使用两个字段,在对象的众多字段中,我倾向于只通过这些字段,其他所有字段都相同。

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

另一种选择是使其成为类方法,因此您不必传递任何东西。测试不太方便,但当您的方法仅用于 TaxForm 对象的数据时值得考虑。

于 2009-07-07T14:30:51.593 回答
1

我意识到这在很大程度上是所用示例的产物,因此它可能不适用于许多实际情况,但是,如果该函数与特定类的联系如此紧密,那么它不应该是:

double payment = f.calculateTaxesOwed;

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

于 2009-07-07T15:02:52.273 回答
0

第一种形式的一个优点是

  1. 抽象 - 编程到接口而不是实现。从长远来看,它使您的代码维护更容易,因为只要 TaxForm 的接口不改变,您就可以更改 TaxForm 的实现而不影响客户端代码。
于 2009-07-07T14:28:24.000 回答
0

这与 Martin Fowler 关于重构的书中的“Introduce Parameter Object”相同。如果有一组参数倾向于一起传递,Fowler 建议您执行此重构。

于 2009-07-07T14:31:06.227 回答
0

如果您相信得墨忒耳定律,那么您会赞成准确地传递所需的内容:

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

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

于 2009-07-07T14:34:06.957 回答
0

分离 UI 和要操作的数据

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


一般讨论,使用示例:

考虑一个为名片创建位图的函数。是函数的目的

(1) // 根据名字和姓氏格式化名片标题

或者

(2) // 格式化记录中的名片标题Person

第一个选项更通用,耦合更弱,这通常是可取的。但是,在许多情况下,对变更请求的鲁棒性较差 - 例如考虑“案例 2017:将人员初始添加到名片”。

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

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

如果那是“未决定的”,并且您不能出于目的(1)或(2)而选择,我宁愿选择(2),以保持句法清洁。

于 2009-07-07T14:41:29.580 回答
0

如果让我选择两者之一,我总是会选择第二个 - 如果您发现您(无论出于何种原因)需要计算所欠税款,但您没有实例TaxForm怎么办?

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

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

double calculateTaxesOwed(TaxForm f) 
{
    return calculateTaxesOwed(f.getTaxRate(), f.getIncome());
}

double calculateTaxesOwed(double taxRate, double income) { ... }

我的经验法则是尽可能有一个方法可以准确地接受它需要的输入——它很容易编写包装器方法。

于 2009-07-07T16:21:00.943 回答
0

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

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

于 2009-07-07T16:30:38.990 回答