是否可以只使用具有属性的类进行重构?


79

我有一个采用30个参数的方法。我将参数放入一个类中,这样我就可以将一个参数(该类)传递给方法。在重构的情况下,传递封装了所有参数的对象(即使它包含了所有参数)是否完美?


下面的答案是好的。我总是为此目的开设一门课。您正在使用自动属性吗? msdn.microsoft.com/zh-CN/library/bb384054.aspx
Harv

否则称为POD(普通数据)类型。
2011年

17
尽管给出的许多答案很好,并且将这些参数重构为更易于处理的确是一个好主意,但我要说的是,此方法需要30个不同的数据才能完成工作,这是一个事实它做得太多了。但是话又说回来,我们还没有看到有问题的方法,甚至没有知道它的用途。
用户

1
这样使用了所有参数的方法,您是否将其移至新类中?还是旧方法只是将新类作为其单个参数?如果可以的话,我会考虑将该方法放在新类中。只包含数据而没有方法的类称为贫血类,感觉OO少了一些。但是,这不是一个硬性规定,只是要注意的事情。
库尔特(Kurt)

@迈克尔:我同意你的看法。还有一点-我相信您不应该创建方法专用对象,除非其内部在方法外部具有某种关系。
土星技术公司

Answers:


75

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

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

正如David Heffernan所提​​到的,它可以帮助自己编写代码:

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

大。我的印象是,只有具有属性且没有方法的类是没有用的。
Xaisoft

2
如果它将仅包含数据成员,则我希望将其构造为struct。使用struct给人的印象就是它只是数据。
Yousf 2011年

28
使用struct具有副作用,例如对传递的语义进行复制。在做出这样的决定之前,请确保您已经意识到这一点
Adrian Zanescu 2011年

这就是javascript模块通常的编写方式,并且随着模块的成熟,它似乎是接受更多或更少参数的最佳方法。someFunction({myParam1:'something',myParam2:'somethingElse'});
克里斯蒂安(Kristian)2012年

63

尽管这里的其他答案正确地指出,传递类的实例比传递30个参数更好,但是请注意,大量参数可能是潜在问题的征兆。

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

或者,寻找将参数分组为更高抽象级别的对象的方法。IMO不得已将一堆不相关的参数转储到一个类中。

请参阅多少个参数太多?有关此的更多想法。


这些参数在什么意义上无关?它们都被此方法使用。那是非常牢固的关系。此外,堆栈上的参数通常更适合陈述状态。考虑多线程。
David Heffernan

12
如果不看OP的代码就无法回答这个问题,但是我不同意仅仅因为两个值在方法中一起使用,它们之间就具有很强的联系。如果这是真的,那么OO设计最终将意味着创建一个大类,该大类包含应用程序中使用的所有可能的属性。
D'Arcy Rittich

不,您说得对,不一定相关。但也不一定无关。因此,正如您所说,需要确定代码。
David Heffernan

23
30个参数?我会选择最可能无关的方法,也可能表明该方法太长,圈复杂度高并且是bug的避风港。考虑一下,我们正在谈论的是一种行为至少具有30个维度可以变化的方法。如果有针对此方法的单元测试,我不想成为编写它们的TBH的人。
史蒂夫·罗伯瑟姆

3
另一个可能的情况是所有参数都相关,但是它们的组织性很差。参数组很可能应该捆绑到不同的对象中,而不是零散地传递。假设我有一个对“人”,“地址”,“处方”等信息进行操作的方法,如果将每条离散的信息分别传递到我的方法中,则很容易达到30个参数。
Nate CK

25

这是一个好的开始。但是,既然您已经拥有了这个新类,请考虑将您的代码由内向外。将将该类作为参数的方法移到新类中(当然,将原始类的实例作为参数传递)。现在,您已经拥有了一个大方法,仅在一个类中就可以了,将它分解成更小,更易管理,可测试的方法会更容易。其中一些方法可能会移回原始类,但相当一部分可能会保留在您的新类中。您已经超越了“引入参数对象”,可以用“方法对象代替“方法”

具有三十个参数的方法是一个很明显的信号,表明该方法太长且太复杂。太难调试,太难测试。因此,您应该对此做些事情,“引入参数对象”是一个很好的起点。


4
这绝对重要!创建这些属性束之所以很棒,是因为这是使用自己的方法创建新类的第一步,并且您几乎总是会找到属于您新类的方法-寻找它们!我认为这是该组中最关键的答案,应该接近顶部。
Bill K

15

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

不能真正解决的问题之一就是Feature Envy。通过参数对象传递的类对另一个类的数据如此感兴趣的事实,是否并不表示也许对该数据进行操作的方法应该移到数据所在的位置?最好识别属于同一类的方法和数据并将它们分组为类,从而增加封装并使代码更灵活。

在将行为及其所处理的数据分成单独的单元进行了几次迭代之后,您应该发现您不再拥有具有大量依赖关系的类,这总是一个更好的最终结果,因为它会使您的代码更加柔软。


10

这是一个绝妙的主意,也是解决该问题的非常普遍的解决方案。参数超过2或3的方法越来越难以理解。

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

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

自然,当您有很多参数时,基于位置标识的替代方法简直就是恐怖。

另一个好处是,可以在不强制所有呼叫站点进行更改的情况下,向接口协定中添加额外的参数。但是,这并不总是看起来那么琐碎。如果不同的呼叫站点需要不同的新参数值,那么与使用基于参数的方法相比,很难找到它们。在基于参数的方法中,添加新参数会强制每个调用站点进行更改以提供新参数,并且您可以让编译器完成查找所有参数的工作。


9

马丁·福勒(Martin Fowler)在他的《重构》一书中将此称为“引入参数对象” 。有了这种引用,很少有人会认为这是一个坏主意。


1
是的,但是对于30个参数,在其他地方出了点问题,必须首先解决
manojlds

5

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


3

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

但是,您要尝试做的事情很常见,也是个好主意!


我实际上考虑过使用结构,但是我很好奇如果使用结构是否会有任何不利之处。
Xaisoft 2011年

为什么选择大卫?字段数与它有什么关系?只是好奇。
塔德·多纳格

2
作为性能问题。虽然从语义上讲,在这里使用结构更有意义,因为仅每个字段的值很重要,并且引用标识无关紧要,大约30多个字段需要复制(例如,它们大多数是引用类型; 32位为120字节,而32位为240字节)。 64)而不是一个类(4或8个字节)。但是,结构的按值复制本质意味着一旦通过访问进行复制将比使用引用类型快,因此结构效率较低的阈值高于上述1指针大小。大于4时,指针大小会考虑它的时间。
乔恩·汉纳,

太棒了 感谢您的解释!:)
Tad Donaghe 2011年

3

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


我不完全记得,但是我想当我不久前在这里提到某个地方时,我正在创建一个仅包含属性的类,当时它被看不起。关于第二点,您是说只应将未更改的参数传递给方法,换句话说,如果方法更改了参数,则不应将其作为参数传递。
Xaisoft

仅仅是属性的类可能会发出难闻的气味(请参阅Steve Rowbotham的答案),表示应该是“完整”类。一个好兆头是,如果您最终多次使用相似的类。但是,情况并非总是如此,并且在30场类和30参数方法之间进行折衷时,有一个很好的要求,就是要倾向于前者。此外,这可能是建立“完整”类的开始。
乔恩·汉娜

...我删除了对不变性的评论,因为我想更多地将其视为仅是公共方法的类(“请求”对象描述了调用者的意图)。在这里更改“请求”可能会导致混乱。在一次性情况下,可变并随您构建便更方便。不变性只是意味着用30个参数的构造函数替换30个参数的调用,然后再进行调用,因此不会有成功。
乔恩·汉娜

3

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

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

这就是Paint.NET中的save save-dialog的工作方式,因此这是一个非常真实的示例。


3

如前所述:这是正确的步骤,但也请考虑以下几点:

  • 您的方法可能太复杂了(您应考虑将其分为更多方法,甚至将其变成单独的类)
  • 如果为参数创建类,则使其不可变
  • 如果许多参数可能为null或具有某些默认值,则可能要对类使用构建器模式

0

这里有很多很棒的答案。我要加两分钱。

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

/ 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气味,从而使您感到舒适。:-)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.