是否可以使用仅具有重构属性的类?

时间:2011-11-10 17:01:40

标签: c# oop refactoring

我有一个需要30个参数的方法。我把参数放到一个类中,这样我就可以将一个参数(类)传递给方法。在重构的情况下传递一个封装所有参数的对象是完全没问题,即使它包含所有参数。

12 个答案:

答案 0 :(得分:75)

这是个好主意。例如,通常如何在WCF中完成数据合同。

此模型的一个优点是,如果添加新参数,则类的使用者不需要仅更改以添加参数。

正如David Heffernan所提​​到的,它可以帮助自我记录代码:

FrobRequest frobRequest = new FrobRequest
{
    FrobTarget = "Joe",
    Url = new Uri("http://example.com"),
    Count = 42,
};
FrobResult frobResult = Frob(frobRequest);

答案 1 :(得分:62)

虽然这里的其他答案正确地指出传递类的实例比传递30个参数更好,但要注意大量参数可能是潜在问题的症状。

例如,很多时候静态方法的参数数量会增长,因为它们应该一直是实例方法,并且您传递的信息很多,可以更容易地在该类的实例中维护。

或者,寻找将参数分组到更高抽象级别的对象的方法。将一堆不相关的参数转储到单个类中是IMO的最后手段。

有关此问题的更多建议,请参阅How many parameters are too many?

答案 2 :(得分:25)

这是一个好的开始。但是现在你已经有了这个新课程,请考虑将你的代码从里到外。将将该类作为参数的方法移动到新类中(当然,将原始类的实例作为参数传递)。现在你已经有一个很大的方法,单独在一个类中,并且更容易将它分成更小,更易于管理,可测试的方法。其中一些方法可能会回到原来的类,但是一个公平的块可能会留在你的新课程中。您已将Introduce Parameter Object移至Replace Method with Method Object

使用具有30个参数的方法是一个非常强烈的迹象,表明该方法太长且太复杂。太难调试,太难测试了。所以你应该对它做些什么,并且引入参数对象是一个很好的起点。

答案 3 :(得分:14)

虽然重构参数对象本身并不是一个坏主意,但它不应该用于隐藏需要从其他地方提供的30个数据的类仍然可能具有代码味道的问题。引入参数对象重构应该被视为更广泛的重构过程中的一个步骤,而不是该过程的结束。

它没有真正解决的一个问题是Feature Envy。传递参数对象的类是否对另一个类的数据如此感兴趣这一事实是否表明可能将对该数据进行操作的方法移动到数据所在的位置?最好是识别属于一起的方法和数据集群,并将它们分组到类中,从而增加封装并使代码更加灵活。

经过几次迭代分离行为及其操作的数据进入单独的单元后,你会发现你不再拥有任何具有大量依赖关系的类,这总是更好的最终结果,因为它会使你的代码更加柔和

答案 4 :(得分:10)

这是一个很好的想法,也是这个问题的一个非常常见的解决方案。超过2或3个参数的方法指数越来越难以理解。

将所有这些封装在一个类中可以使代码更加清晰。因为您的属性具有名称,所以您可以编写自我记录代码,如下所示:

params.height = 42;
params.width = 666;
obj.doSomething(params);

当你有很多参数的时候,基于位置识别的替代方案就是可怕的。

另一个好处是可以在不强制更改所有呼叫站点的情况下,为接口合同添加额外的参数。然而,这并不像看起来那么简单。如果不同的呼叫站点需要不同的新参数值,那么比使用基于参数的方法更难捕获它们。在基于参数的方法中,添加新参数会强制在每个调用站点进行更改以提供新参数,您可以让编译器完成所有查找的工作。

答案 5 :(得分:9)

Martin Fowler在他的书 Refactoring 中称之为Introduce Parameter Object。有了这个引用,很少有人会认为这是一个坏主意。

答案 6 :(得分:5)

30个参数是一团糟。我认为拥有属性的类更漂亮。您甚至可以为适合同一类别的参数组创建多个“参数类”。

答案 7 :(得分:3)

您还可以考虑使用结构而不是类。

但是你要做的事情非常普遍且很棒!

答案 8 :(得分:3)

无论您是否正在重构,使用Plain Old Data类都是合理的。我很好奇为什么你认为它可能不是。

答案 9 :(得分:3)

也许C#4.0的可选参数和命名参数可以替代它?

无论如何,您描述的方法也可以用于抽象程序行为。例如,您可以在接口中使用一个标准SaveImage(ImageSaveParameters saveParams) - 函数,其中ImageSaveParameters也是一个接口,并且可以根据图像格式具有其他参数。例如,JpegSaveParameters具有Quality - 属性,而PngSaveParameters具有BitDepth - 属性。

这就是Paint.NET中的保存保存对话框的功能,因此它是一个非常真实的例子。

答案 10 :(得分:3)

如前所述:这是正确的步骤,但也要考虑以下事项:

  • 您的方法可能过于复杂(您应该考虑将其划分为更多方法,甚至将其转换为单独的类)
  • 如果您为参数创建了类,请将其设为immutable
  • 如果许多参数可能为null或者可能有某些默认值,您可能希望为您的班级使用builder pattern

答案 11 :(得分:0)

这里有很多很棒的答案。我想补充两分钱。

参数对象是一个好的开始。但还有更多可以做的事情。请考虑以下(ruby示例):

/ 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。 : - )