Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#4.0

Specification pattern in C#

4.72/5 (63 votes)
1 Dec 2014CPOL7 min read 164.9K   2.4K  
This article explains how to compose Linq queries in C# using Specification pattern.

Introduction

In this article, we are going to explore the classic specification pattern and implement it in order to compose the LINQ queries, and also non-LINQ queries. This article will also help beginners in understanding the specification pattern and how to implement it in practice. Major benefits of the specification patterns include reusability, maintainability, loose coupling of business rules from the business objects, readability and easy testing.

Specification Pattern

Specification pattern as per the definition from Wikipedia, is a particular software design pattern, whereby business rules can be recombined by chaining the business rules together using boolean logic. To put it simply, business rules are segregated based on the Single responsibility principle (SRP) and chained or composed using boolean operands (AND, OR or NOT) to achieve the desired result. Each segregated business rule is called Specification.

Each specification inherits the abstract CompositeSpecification class which defines one abstract method called IsSatisfiedBy. This method is the engine of specification pattern. The responsibility of this method is to apply the business rule (specification) on the object in question and return a boolean result. The primary goal of the specification pattern is to select the subset of objects that satisfy a set of specifications chained together.

Image 1

ISpecification<T>

The interface ISpecificaton<T> is where the definition of a specification lives. Each specification should implement four methods IsSatisfiedby(), And(), Or(), and Not(). As mentioned earlier, the IsSatisfiedBy method does the main function of applying the business rule on the objects. The And, Or, and Not boolean operator methods help to chain the specifications together in order to create a composite business rule. The specification pattern is not constrained to these three boolean operators (i.e.) there can also be methods like AndOr, AndNot, etc. as required (but chaining And and Or gives AndOr, so having such methods are for convenience purposes only). Also, you can un-tie your imagination and define your own chain methods, if you can come up with one.

C#
public interface ISpecification<T>    {
    bool IsSatisfiedBy(T o);
    ISpecification<T> And(ISpecification<T> specification);
    ISpecification<T> Or(ISpecification<T> specification);
    ISpecification<T> Not(ISpecification<T> specification);
}

CompositeSpecification class

The implementation of the And, OR and Not methods are the same across all the specifications, only the IsSatisfiedBy varies based on the business rule. Hence, we define the abstract class called CompositeSpecification that implements the And(), Or(), and Not() methods and leave the IsSatisfiedBy method to its child classes to implement by declaring this method as abstract.

The actual code for the interface and the abstract class can be seen below:

C#
public abstract class CompositeSpecification<T> : ISpecification<T>     
{
    public abstract bool IsSatisfiedBy(T o);

    public ISpecification<T> And(ISpecification<T> specification)       
    {
        return new AndSpecification<T>(this, specification);
    }
    public ISpecification<T> Or(ISpecification<T> specification)        
    {
        return new OrSpecification<T>(this, specification);
    }
    public ISpecification<T> Not(ISpecification<T> specification)       
    {
        return new NotSpecification<T>(specification);
    }
} 

The And, Or and Not methods simply create and return an AndSpecification, OrSpecification, and NotSpecification object respectively. Do not worry about these classes for now as it is discussed in the later sections. If you take a closer look, you can observe that the AndSpecification and OrSpecification classes accept two ISpecification parameters unlike Not which is happy with just one, considering the fact that former ones are binary operators and the later being unary. To illustrate this, consider a case where we need to filter out the mobile phones of brand Samsung and of type Smart phone. We define two specifications each for brand (with check for Samsung) and type (with check for smart). The resulting phone objects should satisfy both these specifications. In order to achieve this, we chain brand and type specifications using And method. Similarly, to select phones that are either of brand Samsung or of type smart, we chain both the specifications with Or method. In either of these cases, we use two specifications. However, to select all other brands except Samsung, we can do with one specification (brand), which is the case for Not method.

Also not that, the And and Or methods substitutes this keyword for the first parameter. The following example illustrate this better.

C#
ISpecification SamsungSmartSpec = samsungBrandSpec.And(smartTypeSpec); 

The And method itself is called from another specification, hence we take advantage of it and use the calling specification as one of the parameter in creating AndSpecification. It also gives more readability then to have something like this.

C#
ISpecification SamsungSmartSpec = dummySpecification.And(samsungBrandSpec, smartTypeSpec);

Specifications that Chain

These specification classes are mainly used for chaining purpose. These classes themselves does not encapsulate any business rules but help the ones that has, to chain together. The code for one of these class that defines "And" boolean operator is shown below:

C#
public class AndSpecification<T> : CompositeSpecification<T>    
{
    ISpecification<T> leftSpecification;
    ISpecification<T> rightSpecification;

    public AndSpecification(ISpecification<T> left, ISpecification<T> right)  {
        this.leftSpecification = left;
        this.rightSpecification = right;
    }

    public override bool IsSatisfiedBy(T o)   {
        return this.leftSpecification.IsSatisfiedBy(o) 
            && this.rightSpecification.IsSatisfiedBy(o);
    }
}

As you can see, the IsSatisfiedBy method calls its counterparts on other specifications (that carries business rules) and combines the results with boolean AND (&&). The left and right specifications, the operands for boolean AND, are assigned during the object instantiation (see the constructor) by the compositeSpecification class as discussed in the previous section. And and Not specification classes are also implemented in a similar manner. Due to length constraint, the code for these classes are not provided here but the complete source code is attached towards the end of this article for you to download.

Expression Specification

We are almost done but not quite finished yet. We have seen the definitions of the specifications and how to chain them together. But what about the actual specification that incorporate the business rules? Here it is…

As we are concentrating on the LINQ based expressions, it is enough to define only one specification that holds a LINQ expression. A LINQ query that takes a condition checks it on an object and returns the boolean result can be expressed as of type Func<T, bool>. Hence our specification class accepts a parameter of such a type. The IsSatisfiedBy method executes this LINQ expression on the object received as the parameter and returns the true or false result. Unless there is no chaining, this method is called by the chaining specification classes ((AndSpecification, OrSpecification, or NotSpecification).

C#
public class ExpressionSpecification<T> : CompositeSpecification<T>   {
    private Func<T, bool> expression;
    public ExpressionSpecification(Func<T, bool> expression)    {
        if (expression == null)
            throw new ArgumentNullException();
        else
            this.expression = expression;
    }

    public override bool IsSatisfiedBy(T o)   {
        return this.expression(o);
    }
}  

Actual Usage

Finally, let us look at the actual usage with a simple example. In our example, I have created a list of Mobile phone objects each with specific brand and type (the source code for these classes can be found in the file attached towards the end of this article). We create specifications to select specific types of phone modes based on type or brand. We also create complex/composite specifications by chaining the simple ones.

C#
class Program
{
    static void Main(string[] args)
    {
        List<Mobile> mobiles = new List<Mobile> { 
            new Mobile(BrandName.Samsung, Type.Smart, 700), 
            new Mobile(BrandName.Apple, Type.Smart), 
            new Mobile(BrandName.Htc, Type.Basic), 
            new Mobile(BrandName.Samsung, Type.Basic) };

        ISpecification<Mobile> samsungExpSpec =
           new ExpressionSpecification<Mobile>(o => o.BrandName == BrandName.Samsung);
        ISpecification<Mobile> htcExpSpec =
           new ExpressionSpecification<Mobile>(o => o.BrandName == BrandName.Htc);
        ISpecification<Mobile> SamsungHtcExpSpec =  samsungExpSpec.Or(htcExpSpec);        
        ISpecification<Mobile> NoSamsungExpSpec = 
          new ExpressionSpecification<Mobile>(o => o.BrandName != BrandName.Samsung);

        var samsungMobiles = mobiles.FindAll(o => samsungExpSpec.IsStatisfiedBy(o));
        var htcMobiles = mobiles.FindAll(o => htcExpSpec.IsStatisfiedBy(o));
        var samsungHtcMobiles = mobiles.FindAll(o => SamsungHtcExpSpec.IsStatisfiedBy(o));
        var noSamsungMobiles = mobiles.FindAll(o => NoSamsungExpSpec.IsStatisfiedBy(o));
    }
}

Here, we defined four different specifications as follows:

  1. Only brand Samsung
  2. Only brand HTC
  3. Either brand Samsung or HTC
  4. All but brand Samsung

The first two specifications are straightforward; a simple expression specification is created with LINQ condition. For the third rule, a composite specification is created by chaining specifications defined for 1 and 2 using And method. The fourth specification is also quite straightforward using Not.

Similarly, we can define more complex specifications by chaining the simple or another complex ones. For example, to find out mobile phones of either brand Samsung or HTC but only of type smart, the query can be composed as follows:

C#
ISpecification<Mobile> complexSpec = (samsungExpSpec.Or(htcExpSpec)).And(brandExpSpec);

Non-LINQ Expressions

In some cases, we may need to define business rules without using LINQ expressions. In such cases, the expressionSpecification class is replaced by number of, simple but different, specification classes equal to number of business rules in question. In our example above, to find out mobile phones of brand Samsung or HTC, we need two different specification classes, one for Samsung brand check and other for HTC brand check. Also note that these specifications are type-specific, in other words, tightly coupled to the object model. For instance, to check the brand of a mobile phone to be Samsung, the specification class needs to know the type of the object in advance. An example non-LINQ specification to evaluate if the mobile phone is a premium or not based on the predefined cost is shown below:

C#
public class PremiumSpecification<T> : CompositeSpecification<T>
{
    private int cost;
    public PremiumSpecification(int cost) {
        this.cost = cost;
    }
 
    public override bool IsSatisfiedBy(T o) {
        return (o as Mobile).Cost >= this.cost;
    }
}

As you see, IsSatisfiedBy method needs to know the type of the object in advance (in this case, the object type is Mobile) to access its Cost property.

Mixing It Up

On rare occasions, we may need to mix up both LINQ and non-LINQ expressions in our business logic. As the chaining specification classes (And, Or and Not specifications) are separated from business rule specification classes (expressionSpecification and non-LINQ specifications), we can chain non LINQ with LINQ expression type specification. In the previous section, we defined a non-LINQ specification. Now we can chain this with LINQ expression as follows:

C#
ISpecification<Mobile> premiumSpecification = new PremiumSpecification<Mobile>(600);
ISpecification<Mobile> linqNonLinqExpSpec = NoSamsungExpSpec.And(premiumSpecification); 

This will fetch all non-Samsung brand phones but only of premium ones. In our class, the result is Apple.

A Use case

I have written an article on how to use this pattern with Chain of responsiblity (cor) pattern, or to put it in better words, how to make cor more felxible and reusable using specification pattern. Click here to read.

Thanks a Lot

I would like to give credits to all those people that wrote the articles mentioned below that helped me leverage my knowledge on this topic, without which this article wouldn't be a reality.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)