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...