重构大构造函数 [英] Refactoring large constructors
问题描述
在我们的领域模型中有一些对象,你可以用漫画术语 攻击 大型构造函数,这样IntelliSense就会试图向你展示。 ..
使用50个左右的参数(大多数是值类型,一些引用类型)提示一个类型:
public class MyLegacyType
{
public MyLegacyType(int a1,int a2,int a3,... int a50)// etc
{
}
}
现在我会说一下,没有这种类型不能改变。类型本身在逻辑上代表一个实体,其恰好是属性重的。构造此类型的调用者提供来自多个源的大多数参数,尽管一些是默认的。也许有一个模式来源提供给建设,而不是结果。
但是,可以改变的是如何创建类型。目前我们有部分代码遭受:
- 缺少类型上的智能感知。
- 丑陋和不可读的代码。
- 合并由于
一个直接的答案是利用默认值和命名参数的可选参数来帮助合并中。我们这样做在一定程度上对其他类型,工作确定。
但是,感觉就像这是完全重构的一半。
另一个明显的解决方案是使用具有用于构造函数参数的属性的容器类型来减少构造函数参数。这很好地整理了构造函数,并允许你在容器中嵌入默认值,但实质上是将问题移动到另一个类型上,并且可能与可选/命名的参数用法相同。
还有一个Fluent构造函数的概念...在每个属性( WithIntA
, WithIntB
)或容器类型( WithTheseInts(IntContainer c)
)。就我个人而言,我喜欢这种方式从呼叫方,但再次对一个大型的它会变得冗长,感觉就像我刚刚移动一个问题,而不是解决一个。
我的问题,如果有一个埋在这个混乱,是:这些可行的重构战术的问题吗?请提供任何答案有一些相关的经验,陷阱或批评。我倾向于流利的东西,因为我认为它看起来很酷,可读性和合并友好。
我觉得我失去了圣杯的构造函数重构 - 所以我接受建议。当然,这也可能只是一个不幸的,不可避免的副作用,一个类型具有这么多的属性第一次...
显然,我们这里没有太多的上下文,但在50+参数我的解释是这个类做太多,太复杂。我将首先寻找将分块拆分为更简单,更集中的类型的方法 - 然后将每个这些概念的实例封装到复合类中。因此它变成:
public MyLegacyType(SomeConcept foo,AnotherConcept bar,...)
{
}
其中只有在概念之间进行协调所需的逻辑保留在 MyLegacyType
( SomeConcept
的任何逻辑特性到那里,等等)。
这不同于你的使用容器类型来减少构造函数参数,这些容器类型具有用于构造函数参数的属性,因为我们从根本上重构了逻辑 - 不只是使用对象来替换构造函数参数。
We have a few objects in our domain model with what you would comically term offensively large constructors, so large that IntelliSense gives up trying to show it all to you...
Cue a type with 50 or so arguments, mostly value types, a few reference types:
public class MyLegacyType
{
public MyLegacyType(int a1, int a2, int a3, ... int a50) // etc
{
}
}
I'll say it now, no this type cannot change. The type itself logically represents one entity, which happens to be property-heavy. Callers constructing this type provide the majority of the arguments from multiple sources, though some are defaulted. Perhaps there is a pattern for the sources to be provided to construction instead of the results.
However, what can change is how the type is created. Currently we have sections of code that suffer from:
- Lack of IntelliSense on the type.
- Ugly and unreadable code.
- Merging pains of due to Connascence of Position.
One immediate answer is to utilise optional parameters for default values and named arguments to help with the merging. We do this to some degree on other types, works ok.
However, it feels as though this is halfway to the full refactoring.
The other obvious solution is to reduce constructor parameters with container types that have properties for what used to be constructor arguments. This tidies the constructors nicely, and allows you to embed default values in the containers, but essentially moves the problem onto another type and possibly amounts to the same as optional / named parameter usage.
There is also the concept of Fluent constructors... both on a per property (WithIntA
, WithIntB
) or container type (WithTheseInts(IntContainer c)
) basis. Personally, I like this approach from the calling side, but again on a large type it gets wordy and feels as though I've just moved a problem instead of solving one.
My question, if there is one buried in this mess, is: are these viable refactoring tactics for the problem? Please flesh any answer out a bit with some relevant experience, pitfalls, or criticisms. I'm leaning towards the Fluent stuff, because I think it looks cool and is quite readable and merge-friendly.
I feel as though I'm missing the Holy Grail of constructor refactorings - so I'm open to suggestions. Of course, this could also just be an unfortunate and unavoidable side effect of having a type with this many properties in the first place...
Obviously we don't have much context here, but at 50+ parameters my interpretation is that this class is doing too much, and is too complex. I would start by looking for ways to split chunks out into simpler, more focused types - and then encapsulate instances of each of those concepts into the composite class. So it becomes:
public MyLegacyType(SomeConcept foo, AnotherConcept bar, ...)
{
}
where only the logic necessary for orchestrating between the concepts remains in MyLegacyType
(any logic particular to SomeConcept
goes there, etc).
This is different to your "reduce constructor parameters with container types that have properties for what used to be constructor arguments", since we are fundamentally restructuring the logic - not just just using an object to replace the constructor arguments.
这篇关于重构大构造函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!