Sometimes the best way to simplify a method signature is to introduce one or more parameter objects - but how can you do this without just moving the complexity somewhere else?

Here are a some techniques that I’ve used to good effect.

Imagine you are working on some kind of document management system and you’ve written a method to search for documents. Incrementally, as you implemented each user story for searching, you ended up with this signature:

public void FindDocuments(
    string titleKeywords, string authorUserName, 
    DateTime? createdFrom, DateTime? createdUntil,
    DateTime? updatedFrom, DateTime? updatedUntil,
    bool includeDeleted, bool includeInaccessible) { ... }

The code works - but this method signature is pretty poor. We have two strings, one for keyword searching of document titles and one for the username of someone who must have been an author. We may specify a range of time during which the document was created and we may specify a range of time during which the document was last updated. Or both. Or neither. We can also request that deleted documents should be returned in the result set and/or that documents we’re not permitted to access should be returned.

Let’s work to improve it.

Perhaps the first code smell to address is the repeated use of pairs of DateTime values to represent a period - or span - of time. Whenever you have a common set of parameters that always appear together, that’s a good indicator of a missing abstraction, a concept that hasn’t been extracted into a class of its own.

Let’s introduce a simple TimePeriod class (you could make it a struct) that represents such a span.

public class TimePeriod {
    public DateTime Start { get; private set; }
    public DateTime Finish { get; private set; }
    public TimePeriod(DateTime start, DateTime finish) { ... }
}

We could also introduce a helper extension method to make creating TimePeriods more convenient.

public static TimePeriod Until(this DateTime start, DateTime finish) { ... }
var period = start.Until(finish);

Our search method signature has now become this:

public void FindDocuments(
    string titleKeywords, string authorUserName, 
    TimePeriod created, TimePeriod updated,
    bool includeDeleted, bool includeInaccessible) { ... }

[This assumes that TimePeriod is a class - if you decided to make it a struct, you’d need to use TimePeriod? (a nullable TimePeriod) instead, to allow for the case where the period isn’t specified.]

In a real system, we’d likely find widespread use for TimePeriod - use wouldn’t be limited to this single query method.

Boolean method parameters are also a strong code smell - in most cases, the presence of a boolean parameter indicates you actually have two different methods that should be implemented separately. Multiple boolean parameters is even worse

  • but instead of splitting out into many different methods, one alternative is to introduce an enumeration.
[Flags]
public enum FindDocumentOptions {
    None,
    IncludeDeleted = 1,
    IncludeInaccessible = 2
}

public void FindDocuments(
    string titleKeywords, string authorUserName, 
    TimePeriod created, TimePeriod updated,
    FindDocumentOptions options) { ... }

We’ve made our method signature much better, but it’s still not as good as we might hope.

Let’s introduce a parameter object to replace these many parameters with one.

public void FindDocuments( DocumentSearch search) { ... }

But, haven’t we just moved the complexity to the constructor of the parameter object?

public class DocumentSearch {
    public DocumentSearch(
        string titleKeywords, string authorUserName, 
        TimePeriod created, TimePeriod updated,
        FindDocumentOptions options) { ... }
}

Not necessarily - our parameter object could support a number of configuration methods, allowing it to be set up across multiple lines of code.

One way to do this would be with the classic mutable class:

var search = new DocumentSearch();
search.FindInTitle(keywords);
search.FindAuthor(author);
search.CreationPeriod = start.Until(finish);
var result = FindDocuments(search);

If you have a preference for immutable objects, the parameter object pattern can still be used. Simply include a protected copy constructor and return a new instance from each configuration method.

public class DocumentSearch {
    public DocumentSearch() { ... }

    public DocumentSearch InTitle(string keywords) {
        var result = new DocumentSearch(this);
        result.TitleKeywords = keywords;
        return result;
    }

    public DocumentSearch WithAuthor(string author) { ... }

    public DocumentSearch CreatedDuring(TimePeriod period) { ... }

    public DocumentSearch UpdatedDuring(TimePeriod period) { ... }
}

With this configuration object, our search would look like this.

var search 
    = new DocumentSearch()
        .InTitle(keywords)
        .WithAuthor(author)
        .CreatedDuring(start.Until(finish));
var result = FindDocuments(search);

Comments

blog comments powered by Disqus