【问题标题】:Refactoring large constructors重构大型构造函数
【发布时间】:2011-08-31 11:02:06
【问题描述】:

我们的域模型中有一些对象,您可笑地称之为 冒犯性 大型构造函数,如此之大以至于 IntelliSense 放弃了向您展示所有内容的尝试。 ..

用 50 个左右的参数提示一个类型,主要是值类型,一些引用类型:

public class MyLegacyType
{
    public MyLegacyType(int a1, int a2, int a3, ... int a50) // etc
    {
    }
}

我现在就说,不,这种类型不能改变。类型本身在逻辑上代表一个实体,它恰好是重属性的。构造此类型的调用者提供来自多个来源的大部分参数,尽管有些是默认的。也许有一种模式可以将源提供给构造而不是结果。

但是,可以改变的是类型的创建方式。目前我们有部分代码存在以下问题:

一个直接的答案是使用可选参数作为默认值和命名参数来帮助合并。我们在某种程度上对其他类型这样做,工作正常。

然而,感觉这似乎是完全重构的一半。

另一个明显的解决方案是使用容器类型来减少构造函数参数,这些容器类型具有过去是构造函数参数的属性。这很好地整理了构造函数,并允许您在容器中嵌入默认值,但本质上将问题转移到另一种类型上,并且可能与可选/命名参数用法相同。

还有 Fluent 构造函数的概念……基于每个属性(WithIntAWithIntB)或容器类型(WithTheseInts(IntContainer c))。就个人而言,我喜欢调用方的这种方法,但是在大字体上它又会变得冗长,感觉好像我只是移动了一个问题而不是解决一个问题。

我的问题是:这些是解决问题的可行重构策略吗?请用一些相关的经验、陷阱或批评来充实任何答案。我倾向于使用 Fluent 的东西,因为我认为它看起来很酷,而且可读性强且易于合并。

我觉得我好像错过了构造函数重构的圣杯——所以我愿意接受建议。当然,这也可能是一个不幸且不可避免的副作用,即首先拥有具有这么多属性的类型...

【问题讨论】:

  • 对于可选的构造函数参数,我会采用您的第二种方法(具有用于过去构造函数参数的属性的容器类型)。流利的接口很好,但如果你必须链接很多方法调用 IMO 会变得有点混乱

标签: c# c#-4.0 refactoring


【解决方案1】:

显然我们在这里没有太多的上下文,但在 50 多个参数时,我的解释是这个类做的太多,太复杂了。我会首先寻找将块拆分为更简单、更集中的类型的方法 - 然后将这些概念中的每一个的实例封装到复合类中。于是就变成了:

public MyLegacyType(SomeConcept foo, AnotherConcept bar, ...)
{
}

只有在概念之间编排所需的逻辑仍保留在MyLegacyType 中(SomeConcept 特有的任何逻辑都放在那里,等等)。

这与您的“使用具有曾经是构造函数参数的属性的容器类型来减少构造函数参数”不同,因为我们从根本上重构了逻辑 - 而不仅仅是使用一个对象来替换构造函数参数。

【讨论】:

  • 我在这里查看了 Composition,但本质上,该类型正在执行一项任务 - 一个碰巧具有大量属性的实体的表示。正如你所说,这是由于缺乏背景,我会看看我是否可以用它来修改我的问题。
  • 实际上采取了这个想法,如果你把它转过来,从调用者的上下文中组合一个项目,构造函数的参数可以改变以适应。在一个示例中,项目由代码的 DAL 区域构建,并提供所有属性。这些可以包装在满足所有属性的提供类型中。其他调用者调用特定的借口,其中某些属性总是默认的,这些可以使用不同的构造函数容器来隐藏这个事实。我相信一定有一个模式。
【解决方案2】:

我会使用容器类型并使用 C# 4.0 的直接属性分配。这样一来,人们就可以轻松地在生成的类型上使用 Intellisense,同时仍然保持与原始类型的良好解耦。

例如:

public class MyLegacyType
{
    public MyLegacyType(MyConfiguration configuration) // etc
    {
      // ...
    }
}

public class MyConfiguration
{
   public int Value1 { get; set; }
   public int Value2 { get; set; }
   // ...
}

然后:

var myInstance = new MyLegacyType(new MyConfiguration
{
  Value1 = 123,
  Value2 = 456
});

【讨论】:

    【解决方案3】:

    关于您的问题,我不确定一件事,那就是您为什么要在构造函数中使用所有此类参数?您是否使用了构造函数代码中的所有参数?您对智能感知的问题可能来自单个方法的参数过多。在单一类型上拥有许多字段/属性不会导致任何问题。

    您似乎已经看到了一些管理 args 数量的方法,但是如果您可以解释为什么需要在构造函数中接收所有这些,我们可以跳出这个框框思考。那里可能有一些东西可以看。

    【讨论】:

    • 构造函数中所有参数的原因之一是,这是我们目前最坏的情况,也是遗留编码风格的结果,因此 DAL 代码可以从阅读器构造对象。我认为没有任何其他“足够好”的原因,我认为这只是人们不重构代码而只是附加代码的受害者。大多数属性只是简单地从构造函数转置到内部字段。
    • 为什么不使用属性来填充 DAL 中的实体字段?或者,如果您需要它在单个 C# 语句中,您可以在 C# 中使用对象初始化器语法 - new ClassName { Prop1 = value, Prop2 = value, ... }
    • 并非所有属性都公开设置器。我认为它以这种方式完成的唯一原因是因为它一直是这样做的——没有人自己改变风格。实际上,我们最糟糕的类型之一是由 DAL 专门使用的,唯一调用它的地方是我们的测试项目。
    • 我认为重构和使用组合或使用流畅的构造函数的替代方法是公开设置器(如果 DAL 在同一个程序集中,可能具有内部可见性)并更改填充对象的方式.你不需要任何复杂的模式来使你的代码更好。
    • 关于通过使用ctors而不是属性来保证有效/完整实例的实例化有一些话要说......
    猜你喜欢
    • 1970-01-01
    • 2017-10-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-30
    相关资源
    最近更新 更多