Improving the readability of the Specification pattern


Share

One pattern I’ve grown to love, as it provides an expressive and maintainable way to remix business logic, is the specification pattern. Using a specification, I can define business rules against an object as single statements of truth, and compose more complicated rules out of those smaller rules. Here’s a quick example:

// Specification
class ShortWordSpecification : SpecificationBase<string>
{
    public override bool IsSatisfiedBy(string instance)
    {
        return instance.Length < 5;
    }
}

// Usage
var spec = new ShortWordSpecification();
bool result = spec.IsSatisfiedBy("GOOG");

In the example above I have defined what my application considers to be a short word. Using composition in my base specification class, I can form a larger business rule out of this, and other specifications:

class StockSymbolSpecification : SpecificationBase<string>
{
    public override bool IsSatisfiedBy(string instance)
    {
        return new UppercaseSpecification()
            .And(new ShortWordSpecification())
            .IsSatisfiedBy(instance);
    }
}

While the value of a specification is clear, I find the usage backwards to how I like to work with object instances. In other words, rather than invoking the specification’s IsSatisfiedBy method and passing in the instance I’m working with, I should be able to write an extension method to call on my object instance directly, passing along the specification to use. A first attempt to do so might look like this:

// Extension method
public static bool Satisfies<T, K>(this T instance, K specification)
    where K : ISpecification<T>
{
    return specification.IsSatisfiedBy(instance);
}

// Usage
"GOOG".Satisfies(new StockSymbolSpecification());

Now we’re able to call our specifications against the instance, which is an improvement from the previous usage, but we can do better. Even though instantiating a new specification every time it is needed is a cheap operation, it introduces noise in an otherwise compact and expressive API, so let’s find a way to remove it. Because specifications are strongly typed to their test instances, an attempt to refactor our extension method will introduce more noise, because we will need to provide an additional generic type parameter:

// Extension method
public static bool Satisfies<T, K>(this T instance)
 where K : class, ISpecification<T>
{
 var specification = Activator.CreateInstance<K>();
 return specification.IsSatisfiedBy(instance);
}

// Usage
"GOOG".Satisfies<string, ShortWordSpecification>();

We no longer have an instantiation in our code, but we’ve added a redundant type parameter. This is necessary because in our previous iteration we provided both T and K generic values within the method signature itself through the instance and the newly constructed specification, making the type declaration redundant. Now, we’ve left out what specification we want, and since that’s required information, we now need to declare the full generic signature, including the instance type we already know. To remove this artifact, we’ll need to create a new “marker” interface that our ISpecification<T> implements, so we can work with reflection without types:

// Marker
public interface ISpecification {}

public interface ISpecification<T> : ISpecification
{
    bool IsSatisfiedBy(T instance);

    ISpecification<T> And(ISpecification<T> other);

    ISpecification<T> Or(ISpecification<T> other);

    ISpecification<T> Not();
}

With a non-generic marker interface in place, we can write our new extension method:

public static bool Satisfies<T>(this object instance)
 where T : ISpecification
{
    var marker = Activator.CreateInstance<T>();
    var type = typeof (ISpecification<>).MakeGenericType(instance.GetType());

    if(!marker.Implements(type))
    {
        // Type safety is gone, so do something
        // to ensure T is an ISpecification<typeof(instance)>
        return false;
    }

    var method = type.GetMethod("IsSatisfiedBy");
    var result = method.Invoke(marker, new[] {instance});

    return (bool)result;
}

I borrowed my extension method for checking interface implementations in the method above in order to stand in place of compile-time type checking, as it is now possible to pass any specification to your instance method, not only specifications that were constructed for the specific type. With our new method, the goal of a clean, reversed syntax for specifications is met:

// Old way
var result = new StockSymbolSpecification().IsSatisfiedBy("GOOG");

// New way
var result = "GOOG".Satisfies<StockSymbolSpecification>();

Clearly we have some optimization to do in heavy usage scenarios as we’re leaning on reflection, but we’ve increased the readability of an already excellent approach to validation.

kick it on DotNetKicks.com

Kick It on DotNetKicks.com
blog comments powered by Disqus