是否可以拥有一个仅具有用于重构目的的属性的类?
Posted
技术标签:
【中文标题】是否可以拥有一个仅具有用于重构目的的属性的类?【英文标题】:Is it OK to have a class with just properties for refactoring purposes? 【发布时间】:2011-12-26 08:53:25 【问题描述】:我有一个需要 30 个参数的方法。我将参数放入一个类中,这样我就可以将一个参数(类)传递给方法。在重构的情况下,传入一个封装所有参数的对象是否完全可以,即使它包含的只是它。
【问题讨论】:
下面的答案很好。我总是为此开设一个班级。您是否使用自动属性? msdn.microsoft.com/en-us/library/bb384054.aspx 另外称为 POD(plain-old-data)类型。 虽然给出的许多答案都很棒,而且将这些参数重构为更易于处理的东西确实是个好主意,但我想说的是,这种方法需要 30 个不同的数据做它的事情是它做太多的迹象。但是话又说回来,我们还没有看到有问题的方法,甚至不知道它的用途。 那么那个接受所有参数的方法,你把它移到新类了吗?还是旧方法只是将新类作为其单个参数?如果有意义的话,我会考虑将该方法放入新类中。只有数据没有方法的类被称为贫血类,感觉少了一点OO。不过,这不是一个硬性规定,只是需要看看。 @Michael:我同意你的看法。另一点 - 我相信你不应该创建方法专用对象,除非它的内部在方法之外有某种关系。 【参考方案1】:这是个好主意。例如,这通常是在 WCF 中完成数据协定的方式。
此模型的一个优点是,如果您添加新参数,则类的使用者无需更改即可添加参数。
正如 David Heffernan 提到的,它可以帮助自己记录代码:
FrobRequest frobRequest = new FrobRequest
FrobTarget = "Joe",
Url = new Uri("http://example.com"),
Count = 42,
;
FrobResult frobResult = Frob(frobRequest);
【讨论】:
太棒了。我的印象是,只有一个有属性而没有方法的类是没有用的。 如果它只包含数据成员,我更喜欢让它成为结构。使用 struct 给人的印象是它只是数据。 使用 struct 在传递语义上有副作用,例如复制。请确保您在做出此类决定之前了解这一点 这是javascript模块通常的编写方式,它似乎是在模块成熟时接受或多或少参数的最佳方法。 someFunction( myParam1: 'something', myParam2: 'somethingElse' );【参考方案2】:虽然此处的其他答案正确指出传递类的实例优于传递 30 个参数,但请注意,大量参数可能是潜在问题的征兆。
例如,很多时候静态方法的参数数量会增加,因为它们本来就应该是实例方法,而您传递的很多信息可以更容易地在该类的实例中维护。
或者,寻找将参数分组到更高抽象级别的对象的方法。将一堆不相关的参数转储到一个类中是 IMO 的最后手段。
请参阅How many parameters are too many? 了解更多关于此的想法。
【讨论】:
这些参数在什么意义上是不相关的?它们都被这种方法使用。这是一种非常牢固的关系。更重要的是,堆栈上的参数通常比状态更可取。考虑多线程。 如果没有看到 OP 的代码就无法回答这个问题,但我不同意仅仅因为两个值在一个方法中一起使用,它们就有很强的关系。如果这是真的,OO 设计最终意味着创建一个包含应用程序中使用的所有可能属性的大类。 不,你是对的,不一定相关。但也不一定无关。因此,正如您所说,需要查看代码才能确定。 30 个参数?我会满足于最可能不相关的方法,也可能表明方法太长、圈复杂度高并且是错误的避风港。考虑到我们正在讨论一种方法,它的行为至少有 30 个维度可以变化。如果有针对这种方法的单元测试,我不想成为编写它们的人 TBH。 另一种可能的情况是所有参数都是相关的,但它们的组织很差。参数组很可能应该捆绑到不同的对象中,而不是零散地传递。假设我有一个对诸如“人”、“地址”、“处方”之类的信息进行操作的方法……如果将每个离散的信息分别传递到我的方法中,那么它很容易达到 30 个参数。【参考方案3】:这是一个好的开始。但是现在你已经有了那个新类,考虑把你的代码从里到外。将该类作为参数的方法移动到新类中(当然,将原始类的实例作为参数传递)。现在你有了一个大方法,单独在一个类中,将它分解成更小、更易于管理、可测试的方法会更容易。其中一些方法可能会移回原始类,但相当一部分可能会保留在您的新类中。您已从 Introduce Parameter Object 转到 Replace Method with Method Object。
拥有一个包含 30 个参数的方法是一个非常强烈的迹象,表明该方法太长太复杂。太难调试,太难测试。所以你应该做点什么,Introduce Parameter Object 是一个很好的起点。
【讨论】:
这非常重要!创建这些属性包之所以很棒,只是因为它是使用自己的方法创建新类的第一步,而且您几乎总能找到属于您的新类的方法——寻找它们!我认为这是本组中最关键的答案,应该接近顶部。【参考方案4】:虽然重构为参数对象本身并不是一个坏主意,但它不应该用来隐藏一个需要从其他地方提供的 30 条数据的类仍然可能是代码异味的问题。 Introduce Parameter Object 重构可能应该被视为更广泛的重构过程中的一个步骤,而不是该过程的结束。
它没有真正解决的问题之一是 Feature Envy。传递参数对象的类对另一个类的数据如此感兴趣这一事实是否表明操作该数据的方法可能应该移动到数据所在的位置?识别属于一起的方法和数据集群并将它们分组到类中确实更好,从而增加封装并使您的代码更加灵活。
经过多次迭代,将行为及其操作的数据拆分为单独的单元,您应该会发现您不再拥有任何具有大量依赖项的类,这始终是一个更好的最终结果,因为它会使您的代码更加灵活.
【讨论】:
【参考方案5】:这是一个绝妙的想法,也是解决问题的非常常见的方法。具有超过 2 或 3 个参数的方法变得越来越难以理解。
将所有这些封装在一个类中可以使代码更加清晰。因为你的属性有名字,你可以像这样编写自文档代码:
params.height = 42;
params.width = 666;
obj.doSomething(params);
当然,当您有很多参数时,基于位置识别的替代方案简直是可怕的。
另一个好处是,可以在不强制所有调用站点进行更改的情况下向接口合约添加额外参数。然而,这并不总是像看起来那么简单。如果不同的调用站点需要不同的新参数值,则比使用基于参数的方法更难找到它们。在基于参数的方法中,添加新参数会强制在每个调用站点进行更改以提供新参数,您可以让编译器完成查找所有参数的工作。
【讨论】:
【参考方案6】:Martin Fowler 在他的重构一书中称此为Introduce Parameter Object。有了这个引用,很少有人会认为这是一个坏主意。
【讨论】:
是的,但是对于 30 个参数,其他地方有问题,必须先修复【参考方案7】:30 个参数是一团糟。我认为拥有一个具有属性的类更漂亮。您甚至可以为适合同一类别的参数组创建多个“参数类”。
【讨论】:
【参考方案8】:您也可以考虑使用结构而不是类。
但是您尝试做的事情非常普遍,而且是个好主意!
【讨论】:
其实我也想过用结构体,但是我很好奇如果用结构体会不会有什么坏处。 为什么是大卫?字段数与它有什么关系?只是好奇。 作为性能问题。虽然在语义上在这里使用结构更有意义,因为只有每个字段的值很重要并且引用标识无关紧要,但要复制 30 个左右的字段(比如它们主要是引用类型;32 位为 120 字节,240 字节为64)比一个类(4或8个字节)。但是,结构的按值复制性质意味着一旦通过访问复制将比使用引用类型更快,因此结构效率较低的阈值高于上述 1 指针大小。当指针大小 > 4 时,是时候考虑它了。 太棒了。感谢您的解释! :)【参考方案9】:无论您是否进行重构,使用Plain Old Data 类都是合理的。我很好奇你为什么认为它可能不是。
【讨论】:
我不太记得了,但我想当我前段时间在这里的某个地方提到我正在创建一个只有属性的类时,当时它被看不起。关于你的第二点,你是说只有不改变的参数才应该在方法中传递,换句话说,如果方法改变了参数,它不应该作为参数传递。 只是属性的类可能是一种难闻的气味(请参阅 Steve Rowbotham 的回答),表明应该是“完整”类的东西。一个好的迹象是,如果您最终多次使用类似的课程。然而,情况并非总是如此,在 30 个字段的类和 30 个参数的方法之间的折腾中,倾向于前者是很好的选择。此外,它可以作为构建“完整”课程的开始。 ...我正在删除我对不变性的评论,因为我更多地认为这个类只是一个公共方法(其中“请求”对象描述了调用者的意图)。在这里更改“请求”可能会导致混乱。在一次性的情况下,可变并随用随构建会更方便。不变性只是意味着用 30 参数的构造函数替换 30 参数的调用,然后再调用,所以不会有胜利。【参考方案10】:也许 C# 4.0 的可选参数和命名参数是一个很好的替代方案?
无论如何,您所描述的方法也可以很好地抽象程序行为。例如,您可以在接口中拥有一个标准的SaveImage(ImageSaveParameters saveParams)
函数,其中ImageSaveParameters
也是一个接口,并且可以根据图像格式具有其他参数。例如JpegSaveParameters
有一个Quality
-属性,而PngSaveParameters
有一个BitDepth
-属性。
Paint.NET 中的保存对话框就是这样做的,所以这是一个非常真实的例子。
【讨论】:
【参考方案11】:如前所述:这是正确的步骤,但也要考虑以下几点:
你的方法可能太复杂了(你应该考虑把它分成更多的方法,甚至把它变成一个单独的类) 如果您为参数创建类,请将其设为immutable 如果许多参数可能为 null 或具有某些默认值,您可能希望为您的类使用 builder pattern。【讨论】:
【参考方案12】:这里有很多很棒的答案。我想加两分钱。
参数对象是一个好的开始。但还有更多可以做的。考虑以下(红宝石示例):
/1/ 不是简单地对所有参数进行分组,而是查看是否可以对参数进行有意义的分组。您可能需要多个参数对象。
def display_line(startPoint, endPoint, option1, option2)
可能变成
def display_line(line, display_options)
/2/ 参数对象的属性数量可能少于原始参数数量。
def double_click?(cursor_location1, control1, cursor_location2, control2)
可能变成
def double_click?(first_click_info, second_click_info)
# MouseClickInfo being the parameter object type
# having cursor_location and control_at_click as properties
这样的使用将帮助您发现向这些参数对象添加有意义的行为的可能性。您会发现他们会更快地摆脱最初的Data Class smell,让您感到舒适。 :--)
【讨论】:
以上是关于是否可以拥有一个仅具有用于重构目的的属性的类?的主要内容,如果未能解决你的问题,请参考以下文章
C++ 重构内联 Double 以获得更快的代码 PolyBelp