设计问题:传递你使用的字段还是传递对象?
我经常看到方法接口的两种相互冲突的策略,大致总结如下:
// 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 原则来证明我使用一种形式而不是另一种形式的决定是合理的? 如果您使用动态语言,上述答案是否会改变?
I often see two conflicting strategies for method interfaces, loosely summarized as follows:
// 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());
I've seen advocates for the second form, particularly in dynamic languages where it may be harder to evaluate what fields are being used.
However, I much prefer the first form: it's shorter, there is less room for error, and if the definition of the object changes later you won't necessarily need to update method signatures, perhaps just change how you work with the object inside the method.
Is there a compelling general case for either form? Are there clear examples of when you should use the second form over the first? Are there SOLID or other OOP principles I can point to to justify my decision to use one form over the other? Do any of the above answers change if you're using a dynamic language?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(12)
老实说,这取决于所讨论的方法。
如果该方法在没有对象的情况下也有意义,那么第二种形式更容易重用,并且消除了两个类之间的耦合。
如果该方法依赖于该对象,那么传递该对象就足够了。
对于第三种形式可能有一个很好的论据,您可以传递一个旨在与该方法一起使用的接口。 为您提供第一种形式的清晰度和第二种形式的灵活性。
In all honesty it depends on the method in question.
If the method makes sense without the object, then the second form is easier to re-use and removes a coupling between the two classes.
If the method relies on the object then fair enough pass the object.
There is probably a good argument for a third form where you pass an interface designed to work with that method. Gives you the clarity of the first form with the flexibility of the second.
这取决于你的方法的意图。
如果该方法被设计为专门针对该对象并且仅针对该对象,则传递该对象。 它形成了一个很好的封装。
但是,如果该方法更通用,您可能需要单独传递参数。 这样,当信息来自另一个来源(即不同类型的对象或其他派生数据)时,该方法更有可能被重用。
It depends on the intention of your method.
If the method is designed to work specifically with that object and only that object, pass the object. It makes for a nice encapsulation.
But, if the method is more general purpose, you will probably want to pass the parameters individually. That way, the method is more likely to be reused when the information is coming from another source (i.e. different types of objects or other derived data).
我强烈推荐第二种解决方案 -
calculateTaxesOwed()
计算一些数据,因此需要一些数字输入。 该方法与用户界面绝对无关,因此不应使用表单作为输入,因为您希望业务逻辑与用户界面分离。执行计算的方法(通常)甚至不应该与用户界面属于同一模块。 在这种情况下,您会得到循环依赖,因为用户界面需要业务逻辑,而业务逻辑需要用户界面形式 - 强烈表明出现了问题(但仍然可以使用基于接口的编程来解决)。
更新
如果纳税表单不是用户界面表单,情况会发生一些变化。 在这种情况下,我建议使用
TaxForm
类的实例方法GetOwedTaxes()
或实例属性OwedTaxes
公开该值,但我不会使用静态方法。 如果计算可以在其他地方重用,则仍然可以创建一个使用值而不是表单的静态辅助方法,并从实例方法或属性中调用此辅助方法。I strongly recommend the second solution -
calculateTaxesOwed()
calculates some data, hence needs some numerical input. The method has absolutly nothing to do with the user interface and should in turn not consum a form as input, because you want your business logic separated from your user interface.The method performing the calculation should (usualy) not even belong to the same modul as the user interface. In this case you get a circular dependency because the user interface requires the business logic and the business logic requires the user interface form - a very strong indication that something is wrong (but could be still solved using interface based programming).
UPDATE
If the tax form is not a user interface form, things change a bit. In this case I suggest to expose the value using a instance method
GetOwedTaxes()
or instance propertyOwedTaxes
of theTaxForm
class but I would not use a static method. If the calculation can be reused elsewhere, one could still create a static helper method consuming the values, not the form, and call this helper method from within the instance method or property.我认为这并不重要。 如果您传入对象,您就会面临副作用,因为它可能会发生突变。 然而,这可能正是您想要的。 为了减轻这种情况(并帮助测试),您可能最好传递接口而不是具体类型。 这样做的好处是,如果您想访问对象的另一个字段,则无需更改方法签名。
传递所有参数可以更清楚地了解类型需要什么,并且可能使其更容易测试(尽管如果您使用接口,这没有什么好处)。 但你将会有更多的重构。
根据每种情况的优点来判断,并选择最不痛苦的。
I don't think it really matters. You open yourself to side effects if you pass in the Object as it might be mutated. This might however be what you want. To mitigate this (and to aid testing) you are probably better passing the interface rather than the concrete type. The benefit is that you don't need to change the method signature if you want to access another field of the Object.
Passing all the parameters makes it clearer what the type needs, and might make it easier to test (though if you use the interface this is less of a benefit). But you will have more refactoring.
Judge each situation on its merits and pick the least painful.
仅传递参数可以更容易进行单元测试,因为您不需要仅仅为了测试本质上只是静态计算的功能而模拟充满数据的整个对象。 如果对象的许多字段中只使用了两个字段,那么在其他条件相同的情况下,我倾向于只传递这些字段。
也就是说,当您最终有六个、七个或更多字段时,是时候考虑在“有效负载”类(或结构/字典,具体取决于语言风格)。 长方法签名通常会令人困惑。
另一种选择是使其成为类方法,这样您就不必传递任何内容。 测试不太方便,但当您的方法仅用于 TaxForm 对象的数据时值得考虑。
Passing just the arguments can be easier to unit test, as you don't need to mock up entire objects full of data just to test functionality that is essentially just static calculation. If there are just two fields being used, of the object's many, I'd lean towards just passing those fields, all else being equal.
That said, when you end up with six, seven or more fields, it's time to consider passing either the whole object or a subset of the fields in a "payload" class (or struct/dictionary, depending on the language's style). Long method signatures are usually confusing.
The other option is to make it a class method, so you don't have to pass anything. It's less convenient to test, but worth considering when your method is only ever used on a TaxForm object's data.
我意识到这很大程度上是所使用示例的产物,因此它可能不适用于许多现实情况,但是,如果该函数与特定类紧密相关,那么它不应该是:
它似乎更合适对我来说,税务文件本身将承担计算相关税收的责任,而不是将该责任归于效用函数,特别是考虑到不同的税收形式往往使用不同的税表或计算方法。
I realize that this is largely an artifact of the example used and so it may not apply in many real-world cases, but, if the function is tied so strongly to a specific class, then shouldn't it be:
It seems more appropriate to me that a tax document would carry the responsibility itself for calculating the relevant taxes rather than having that responsibility fall onto a utility function, particularly given that different tax forms tend to use different tax tables or calculation methods.
第一种形式的一个优点是
One advantage of the first form is
这与 Martin Fowler 的重构书中的“引入参数对象”相同。 Fowler 建议,如果有一组参数倾向于一起传递,则执行此重构。
This is the same as the "Introduce Parameter Object" from Martin Fowler's book on refactoring. Fowler suggests that you perform this refactoring if there are a group of parameters that tend to be passed together.
如果您相信德墨忒尔法则,那么您会赞成准确传递所需内容:
http://en .wikipedia.org/wiki/Law_of_Demeter
http://www.c2.com/ cgi/wiki?LawOfDemeter
If you believe in the Law of Demeter, then you would favor passing exactly what is needed:
http://en.wikipedia.org/wiki/Law_of_Demeter
http://www.c2.com/cgi/wiki?LawOfDemeter
将要操作的 UI 和数据分离
在您的情况下,您缺少一个中间类,例如 TaxInfo,代表要征税的实体。 原因是UI(表单)和业务逻辑(如何计算税率)处于两个不同的“变化轨道”,一个是随着呈现技术(“web”、“Web 2.0”、“WPF”、. ..),其他用法律术语进行更改。 在它们之间定义清晰的接口。
一般讨论,使用示例:
考虑一个为名片创建位图的函数。 该函数的目的是
(1) // 根据名字和姓氏格式化名片标题
或
(2) // 根据
Person< 格式化名片标题/code> record
第一个选项更通用,耦合性较弱,通常更可取。 然而,在许多情况下,对变更请求的鲁棒性较差 - 例如考虑“2017 年案例:将人员姓名首字母添加到名片”。
更改实现(添加 person.Initial)通常比更改接口更容易、更快。
最终的选择取决于您期望的更改类型:是否更有可能需要来自
Person
记录的更多信息,或者您是否更有可能希望为其他数据结构创建名片标题?人
?如果这是“未决定”,那么您不能出于目的(1)或(2)进行操作,为了语法的简洁性,我宁愿选择(2)。
Separation of UI and Data to be manipulated
In your case, you are missing an intermediate class, say, TaxInfo, representing the entity to be taxed. The reason is that UI (the form) and business logic (how tax rate is calculated) are on two different "change tracks", one changes with presentation technology ("the web", "The web 2.0", "WPF", ...), the other changes with legalese. Define a clear interface between them.
General discussion, using an example:
Consider a function to create a bitmap for a business card. Is the purpose of the function
(1) // Formats a business card title from first name and last name
OR
(2) // Formats a businnes card title from a
Person
recordThe first option is more generic, with a weaker coupling, which is generally preferrable. However, In many cases less robust against change requests - e.g. consider "case 2017: add persons Initial to business card".
Changing the implementation (adding person.Initial) is usually easier and faster than changing the interface.
The choice is ultimately what type of changes you expect: is it more likely that more information from a
Person
record is required, or is it more likely that you want to create business card titles for other data structures thanPerson
?If that is "undecided", anfd you can't opf for purpose (1) or (2) I'd rather go with (2), for syntactic cleanliness.
如果我被迫选择两者之一,我总是会选择第二个 - 如果您发现您(无论出于何种原因)需要计算所欠税款,但您没有 TaxForm 的实例,该怎么办?
这是一个相当简单的示例,但是我见过一些情况,其中执行相对简单任务的方法具有难以创建的复杂输入,使得该方法比应有的更难使用。 (作者根本没有考虑到其他人可能想要使用该方法!)
就我个人而言,为了使代码更具可读性,我可能会两者兼而有之:
我的经验法则是尽可能有一个完全接受输入的方法它需要 - 编写包装方法非常容易。
If I was made to choose one of the two, I'd always go with the second one - what if you find that you (for whatever reason) need to caculate the taxes owed, but you dont have an instance of
TaxForm
?This is a fairly trivial example, however I've seen cases where a method doing a relatively simple task had complex inputs which were difficult to create, making the method far more difficult to use than it should have been. (The author simply hadn't considered that other people might want to use that method!)
Personally, to make the code more readable, I would probbaly have both:
My rule of thumb is to wherever possible have a method that takes exactly the input it needs - its very easy to write wrapper methods.
就我个人而言,我会选择#2,因为它更清楚该方法需要什么。 通过 TaxForm(如果它是我所认为的那样,就像 Windows 窗体一样)有点臭,让我有点畏缩 (>_<)。
仅当您传递特定于计算的 DTO 时,我才会使用第一个变体,例如 IncomeTaxCalculationInfo 对象,它将包含 TaxRate 和 Income 以及计算方法中的最终结果所需的任何其他内容,但绝不会像 Windows / Web 之类的东西形式。
Personally, I'll go with #2 since it's much more clear of what it is that the method need. Passing the TaxForm (if it is what I think it is, like a Windows Form) is sort of smelly and make me cringe a little (>_<).
I'd use the first variation only if you are passing a DTO specific to the calculation, like IncomeTaxCalculationInfo object which will contain the TaxRate and Income and whatever else needed to calculate the final result in the method, but never something like a Windows / Web Form.