9

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:

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

Adam Houldsworth
  • 63,413
  • 11
  • 150
  • 187
  • 1
    I would go with your second approach (container types that have properties for what used to be constructor arguments) for optional constructor arguments. Fluent interfaces are nice but can get a bit messy if you have to chain a lot of method calls IMO – Daniel Rosendorf Aug 31 '11 at 11:08

3 Answers3

12

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.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • I've looked into Composition here, but in essence the type is doing one task - a representation of an entity that happens to be property-heavy. As you say, that is from a lack of context, I'll see if I can amend my question with that. – Adam Houldsworth Aug 31 '11 at 11:09
  • Actually taking this idea, if you turn it on its head and compose an item from the context of the caller, constructor arguments can be changed to suit. In one example, items are constructed from DAL areas of the code providing all properties without fail. These could be wrapped in a providing type that caters for all properties. Other callers call on specific pretences, where certain properties are always defaulted, these could use different constructor containers to hide this fact. I'm sure there must be a pattern for that. – Adam Houldsworth Aug 31 '11 at 11:16
4

I would go with the container types and use the immediate properties assignment of C# 4.0. This way one could easily use Intellisense on the resulting type, while still retaining a decent decoupling from the original type.

For example:

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

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

And then:

var myInstance = new MyLegacyType(new MyConfiguration
{
  Value1 = 123,
  Value2 = 456
});
Efran Cobisi
  • 6,138
  • 22
  • 22
1

There's one thing that I'm not sure about your question, and that is why do you want all such parameters in the constructor? Are you using all of the parameters in the constructor code? Your problem with the intellisense is probably coming from having too many parameters on a single method. Having many number of fields / properties on a single type won't cause any issues.

It seems that you've seen some ways to manage the number of args, but if you can explain why you need to receive all of them in a constructor, we can think outside this box. There might be something there to look at.

Iravanchi
  • 5,139
  • 9
  • 40
  • 56
  • One of the reasons for all arguments in the constructor, which is currently our worst case and a result of legacy coding styles, is so DAL code can construct the object from a reader. I don't think there are any other "good enough" reasons for it, I think it's just a victim of people not refactoring the code and just appending to it. Most properties are simply transposed from the constructor to the internal field. – Adam Houldsworth Aug 31 '11 at 12:03
  • Why don't you use properties for populating the entity fields in DAL? Or if you need it to be in a single C# statement, you can use object initializer syntax in C# - `new ClassName { Prop1 = value, Prop2 = value, ... }` – Iravanchi Aug 31 '11 at 12:38
  • Not all properties expose setters. I think the only reason it is done in this manner is because it's how it has always been done - no one took it upon themselves to change the style. Actually one of our worst types is exclusively used by the DAL, the only other place it is called from is our test projects. – Adam Houldsworth Aug 31 '11 at 12:43
  • I think an alternative to refactoring and using Composition, or using fluent constructors, is to expose setters (maybe with internal visibility if the DAL is in the same assembly) and change the way you fill in your objects. You don't need any sophisticated patterns to make your code better. – Iravanchi Aug 31 '11 at 13:04
  • Indeed, in fact I'm sort of moving forward with an improvement in that area to at the very least reduce the impact on merging. I decided to make the question more general though to see if I was missing some options on refactoring in general. – Adam Houldsworth Aug 31 '11 at 13:13
  • 1
    There is something to say about guaranteeing the instantiation of valid/complete instances by using ctors instead of properties.. – JefClaes Dec 26 '12 at 15:18