You are writing your API and you start on a method that has more than a few parameters, some of which are just default overrides (optional), you're not breaking SRP and your doing the YAGNI thing but you know its going to grow.
So how do you do it? There are a few choices ...
- Overloads
- Optional Parameters
- A Parameters Object
Overloads
Crikey, this can get painful when you come to add a new parameter.
Take this, you have a method with two optional parameters.
public class SomeServiceV1 {
public void DoSomething(int p1, string p2) { }
public void DoSomething() { }
public void DoSomething(int p1) { }
public void DoSomething(string p2) { }
}You must supply 3 overloads to allow a caller to optionally supply 2 arguments
So you add another optional parameter
public class SomeServiceV2
{
public void DoSomething(int p1, string p2, byte p3) { }
public void DoSomething() { }
public void DoSomething(int p1) { }
public void DoSomething(string p2) { }
public void DoSomething(byte p3) { }
public void DoSomething(int p1, string p2) { }
public void DoSomething(int p1, byte p3) { }
public void DoSomething(string p2, byte p3) { }
}Now you have another 7 overloads on top of the actual method
Then you have to play "follow the spaghetti to find the meatball", when coding it, and, every time you call it in order to see what is actually being supplied to the method.
Optional Parameters
Well I don't think these are the answer at all, at least in C#4.
There are so many pitfalls its going to bite you in the behind.
The Parameters Object
Perfect for this situation, I been using it for years since a colleague tutted it at me during a code review or something.
Basically you replace all you parameters with one object which has properties for those parameters..
public class SomeService
{
public void DoSomething(Parameters parameters) { }
public class Parameters
{
int P1 { get; set; }
int P2 { get; set; }
}
}service.DoSomething(new Parameters {
P1 = 1,
P2 = 10
});- You can add parameters without changing the method signature
(not strictly open/closed but it will do for me) - There is no order for the parameters
- Parameters are optional
- Parameter names are shown in the call
Its all good, but, is it?
Immutable
There has been a lot of press about Immutability and its importance in asynchronous programming, particularly with Windows 8, responsive design, services in the cloud, etc..
You can read all about why its important in the references below, but suffice to say, it is.
edit: here is a line up of relevant articles on immutability.
The parameters object above is not Immutable, where as the parameters directly in the method call that it replaced are.
So how do you achieve an immutable object in C#4?
I came across an old debate on StackOverflow.com which outlines a few methods, and it seems, a favoured way of doing this is to use a Builder.
A Builder object has the ability to collect data about the object it is going to build and then act, in one go, to create that object. It can have special access to the object, such as a private constructor, and therefore is the perfect vehicle to create an immutable object. I have been playing with this type of thing recently and came up with something that I am sure is well known.
But anyway, lets to cut to the chase here...
This is a simple example with two optional parameters
public class SomeOtherService
{
public void DoSomething(DoSomethingParameters doSomethingParameters)
{
}
public class DoSomethingParameters
{
// defaults exposed, so they can be used and seen for what they are
public const int P1_DEFAULT = 1;
public const int P2_DEFAULT = 10;
// validation exposed
public const int P2_MINIMUM = 5;
// immutable fields and properties for the parameters
readonly int _p1;
readonly int _p2;
public int P1
{
get { return _p1; }
}
public int P2
{
get { return _p2; }
}
// a private constructor
DoSomethingParameters(Initializer i)
{
_p1 = i.P1;
_p2 = i.P2;
}
// static create methods,
// where you instinctively go when you can't find a constructor - no?
public static DoSomethingParameters Create(
Action<Initializer> assign)
{
if (assign == null) throw new ArgumentNullException("assign");
var i = new Initializer();
assign(i);
// validation
if (i.P2 < P2_MINIMUM)
throw new ArgumentOutOfRangeException("P2");
return new DoSomethingParameters(i);
}
// the initializer, a mutable transport class
public class Initializer
{
// required params here
public Initializer()
{
// defaults applied here
P1 = P1_DEFAULT;
}
public int P1 { get; set; }
public int P2 { get; set; }
}
}
}
service.DoSomething(
SomeOtherService.DoSomethingParameters.Create(p =>
{
p.P1 = 1;
p.P2 = 10;
})
);You can see adding another ton of optionals is not going to change the complexity of the implementation or the call, and give you a great deal more confidence in your vnext too.
You get intellisense on the create method
The initializer is created for you - just supply a lambda body to assign the values.
You get default values
You get validation
The code for these ramblings is available on GitHub if you want it
References:
http://stackoverflow.com/questions/6239373/how-to-avoid-too-many-parameters-problem-in-api-design
http://codebetter.com/patricksmacchia/2008/01/13/immutable-types-understand-them-and-use-them/
http://msmvps.com/blogs/jon_skeet/archive/2008/03/16/c-4-immutable-type-initialization.aspx
http://weblogs.asp.net/bleroy/archive/2008/01/16/immutability-in-c.aspx

