Coping with Methods with Many Parameters
I will put my captain Obvious cape now and write in short how to get rid of methods with many parameters, especially if some of the parameters are optional, or depend on each other somehow (e.g. go in pairs, one excludes another, etc.).
Let’s take a simple RestClient
class. You need to be able to pass the target resource, optional HTTP headers, HTTP method, request body, timeouts, etc. All of these, except the URL, are optional. How do you write this class?
The first thing that comes to mind is overloading:
public <T> T request(String url); public <T> T request(String url, HttpMethod method, Object body); public <T> T request(String url, HttpMethod method, Object body, Map<String, String> headers); public <T> T request(String url, HttpMethod method, Object body, Map<String, String> headers, long timeout);
Good? No. Because you may want to call a URL with GET, specifying a timeout, but not specifying body or headers. And whenever a new parameter is to be added (e.g. for authentication), then you should copy all the methods and append the parameter. To put it simply: you cannot and should not have an overloaded method for each possible combination of the parameters.
How should we approach the problem? There are several ways, and the one I prefer is using something like the Builder pattern. The Builder pattern is supposed to be used for constructing objects and to replace constructors taking multiple arguments. But its philosophy can be transferred to the above problem. For example:
public <T> T request(RestCall call);
What is RestCall? It’s a mutable object that you configure, preferably with a fluent interface, in order to make your rest call. All fields have getters so that the RestClient
can obtain all the fields in needs. A usage might look like this:
client.request(RestCall.to(url).withMethod(HttpMethod.POST) .withBody(requestObject).withTimeout(5000));
It reads like prose, it lets you have only one (or very few) methods, rather than overload for each possible combination, and at the same time allows you to have each possible combination of parameters. The “to(..)” method is a simple factory-method. An example implementation of a withX method looks like this:
public RestCall withBody(Object body) { this.body = body; //assign a private field return this; }
You can also have reasonable defaults for each field. In this case, a GET method, a predefined default timeout.
This approach also gives you the option to specify constraints. For example, you shouldn’t allow setting a body for GET requests. So:
public RestCall withBody(Object body) { if (method == HttpMethod.GET) { throw new IllegalStateException("Body not supported for GET"); } this.body = body; //assign a private field return this; }
And if two parameters go together, for example you want to add headers one by one, then you can have an addHeader(String, String)
method.
Generally, the approach is more readable and is easier to extend and maintain.