Specification Pattern In C# - Composite Specifications

This article is a continuation of my first article on Specification Pattern in C#. I recommend that you read it for a better understanding of background concepts. In the first part of this article, I introduced the specification pattern and how it can help to avoid violating the ‘O’ of SOLID principles of programming. Here, I’ll basically show how two or more specifications can be combined together to achieve more interesting results.

This article is a continuation of my first article on the Specification Pattern in C#. I recommend you to read it for a better understanding of background concepts.

In the first part of this article, I introduced the specification pattern and how it can help to avoid violating the ‘O’ of SOLID principles of programming. Here, I’ll basically show how two or more specifications can be combined together to achieve more interesting results.

The simplest form of composite specifications are,

  • AND Specification (Returns true when all conditions are met)
  • OR Specification (Returns true when one or more conditions are met)
  • NOT Specification (Returns true when a condition is not met)
  • NOR Specification (Returns true when all conditions are NOT met)
  • NAND Specification (Returns true when one or more conditions are NOT met)

There are actually other ways in which the conditions can be combined and evaluated but these are the easiest to comprehend. For a deeper understanding, you can read about Logic gates. However, I have put together a summary of their truth tables below (for a simple case of two conditions).

Condition 1Condition 2ANDORNOTNORNAND
TTTTFFF
TFFTFFT
FTFTTFT
FFFFTTT

Now, let’s apply them to our specification pattern by combining conditions.

First, we create an abstract CompositeSpecification class that implements the IEmployeeSpecification interface. This serves as the base class from which all our composite specifications inherit. It has a constructor that takes in two parameters of type IEmployeeSpecification (which, of course, can also be a CompositeSpecification and this can be very useful especially when we need to cascade composite specifications or create a composite specification with more than two parameters).

Take a look at the code listing below. I have excluded some details such as the Employee class (as seen in part one of the article - Specification Pattern in C#) for the purpose of brevity.

  1. publicinterfaceIEmployeeSpecification {  
  2.     bool IsSatisfiedBy(Employee employee);  
  3. }  
  4. publicclassEmployeeDepartmentSpecification: IEmployeeSpecification {  
  5.     privatereadonlystring _department;  
  6.     public EmployeeDepartmentSpecification(string depatrment) {  
  7.         _department = depatrment;  
  8.     }  
  9.     publicbool IsSatisfiedBy(Employee employee) {  
  10.         return employee.Department.Equals(_department);  
  11.     }  
  12. }  
  13. publicclassEmployeeYearSpecification: IEmployeeSpecification {  
  14.     privatereadonlyint _year;  
  15.     public EmployeeYearSpecification(int year) {  
  16.         _year = year;  
  17.     }  
  18.     publicbool IsSatisfiedBy(Employee employee) {  
  19.         return employee.YearOfResumption.Equals(_year);  
  20.     }  
  21. }  
  22. publicclassGetEmployee {  
  23.     publicstaticList < Employee > GetEmployeeBy(IEmployeeSpecification specification, Employee[] employees) {  
  24.         List < Employee > NeededEmployees = newList < Employee > ();  
  25.         foreach(Employee employee in employees) {  
  26.             if (specification.IsSatisfiedBy(employee)) {  
  27.                 NeededEmployees.Add(employee);  
  28.             }  
  29.         }  
  30.         return NeededEmployees;  
  31.     }  
  32. }  
  33. // COMPOSITE SPECIFICATIONS  
  34. publicabstractclassCompositeSpecification: IEmployeeSpecification {  
  35.     protectedIEmployeeSpecification specification1;  
  36.     protectedIEmployeeSpecification specification2;  
  37.     public CompositeSpecification(IEmployeeSpecification spec1, IEmployeeSpecification spec2) {  
  38.         specification1 = spec1;  
  39.         specification2 = spec2;  
  40.     }  
  41.     publicabstractbool IsSatisfiedBy(Employee employee);  
  42. }  
  43. publicclassAndSpecification: CompositeSpecification {  
  44.     public AndSpecification(IEmployeeSpecification spec1, IEmployeeSpecification spec2): base(spec1, spec2) {}  
  45.     publicoverridebool IsSatisfiedBy(Employee employee) {  
  46.         return specification1.IsSatisfiedBy(employee) && specification2.IsSatisfiedBy(employee);  
  47.     }  
  48. }  
  49. publicclassOrSpecification: CompositeSpecification {  
  50.     public OrSpecification(IEmployeeSpecification spec1, IEmployeeSpecification spec2): base(spec1, spec2) {}  
  51.     publicoverridebool IsSatisfiedBy(Employee employee) {  
  52.         return specification1.IsSatisfiedBy(employee) || specification2.IsSatisfiedBy(employee);  
  53.     }  
  54. }  
  55. publicclassNORSpecification: CompositeSpecification {  
  56.     public NORSpecification(IEmployeeSpecification spec1, IEmployeeSpecification spec2): base(spec1, spec2) {}  
  57.     publicoverridebool IsSatisfiedBy(Employee employee) {  
  58.         return !(specification1.IsSatisfiedBy(employee) || specification2.IsSatisfiedBy(employee));  
  59.     }  
  60. }  
  61. publicclassNANDSpecification: CompositeSpecification {  
  62.     public NANDSpecification(IEmployeeSpecification spec1, IEmployeeSpecification spec2): base(spec1, spec2) {}  
  63.     publicoverridebool IsSatisfiedBy(Employee employee) {  
  64.         return !(specification1.IsSatisfiedBy(employee) && specification2.IsSatisfiedBy(employee));  
  65.     }  
  66. }  
  67. publicclassNotSpecification: IEmployeeSpecification {  
  68.     privateIEmployeeSpecification specification;  
  69.     public NotSpecification(IEmployeeSpecification spec) {  
  70.         specification = spec;  
  71.     }  
  72.     publicbool IsSatisfiedBy(Employee employee) {  
  73.         return !specification.IsSatisfiedBy(employee);  
  74.     }  
  75. }  

Below is a sample program to test the workings of the code.

Note

You can also download and run the original source code from the top of this article.

  1. classProgram {  
  2.     staticvoid Main(string[] args) {  
  3.         Employee employee1 = newEmployee {  
  4.             FirstName = "Fidel", Department = "Maths", YearOfResumption = 2017  
  5.         };  
  6.         Employee employee2 = newEmployee {  
  7.             FirstName = "Francis", Department = "Software", YearOfResumption = 2016  
  8.         };  
  9.         Employee employee3 = newEmployee {  
  10.             FirstName = "Ahmed", Department = "Maths", YearOfResumption = 2016  
  11.         };  
  12.         Employee employee4 = newEmployee {  
  13.             FirstName = "Ebuka", Department = "Software", YearOfResumption = 2017  
  14.         };  
  15.         Employee[] employees = newEmployee[] {  
  16.             employee1,  
  17.             employee2,  
  18.             employee3,  
  19.             employee4  
  20.         };  
  21.         Console.WriteLine("Software Department");  
  22.         List < Employee > SoftwareEmployees = GetEmployee.GetEmployeeBy(newEmployeeDepartmentSpecification("Software"), employees);  
  23.         foreach(var employee in SoftwareEmployees) {  
  24.             Console.WriteLine(employee.FirstName);  
  25.         }  
  26.         Console.WriteLine("\nEmployed in 2017");  
  27.         List < Employee > EmployedIn2017 = GetEmployee.GetEmployeeBy(newEmployeeYearSpecification(2017), employees);  
  28.         foreach(var employee in EmployedIn2017) {  
  29.             Console.WriteLine(employee.FirstName);  
  30.         }  
  31.         AndSpecification YearAndDepartment = newAndSpecification(newEmployeeDepartmentSpecification("Software"), newEmployeeYearSpecification(2016));  
  32.         Console.WriteLine();  
  33.         Console.WriteLine("\nList of Software Staff Employed in 2016");  
  34.         List < Employee > Software2016Employees = GetEmployee.GetEmployeeBy(YearAndDepartment, employees);  
  35.         foreach(var employee in Software2016Employees) {  
  36.             Console.WriteLine(employee.FirstName);  
  37.         }  
  38.         Console.WriteLine("\nList of Staff either in Software or Employed in 2016");  
  39.         OrSpecification YearOrDepartment = newOrSpecification(newEmployeeDepartmentSpecification("Software"), newEmployeeYearSpecification(2016));  
  40.         List < Employee > SoftwareOr2016Employees = GetEmployee.GetEmployeeBy(YearOrDepartment, employees);  
  41.         foreach(var employee in SoftwareOr2016Employees) {  
  42.             Console.WriteLine(employee.FirstName);  
  43.         }  
  44.         Console.WriteLine("\nList of Staff neither in Software nor Employed in 2016");  
  45.         NORSpecification YearNorDepartment = newNORSpecification(newEmployeeDepartmentSpecification("Software"), newEmployeeYearSpecification(2016));  
  46.         List < Employee > SoftwareNor2016Employees = GetEmployee.GetEmployeeBy(YearNorDepartment, employees);  
  47.         foreach(var employee in SoftwareNor2016Employees) {  
  48.             Console.WriteLine(employee.FirstName);  
  49.         }  
  50.         Console.WriteLine("\nList of Staff who are not in Software or not Employed in 2016 or both");  
  51.         NANDSpecification YearNandDepartment = newNANDSpecification(newEmployeeDepartmentSpecification("Software"), newEmployeeYearSpecification(2016));  
  52.         List < Employee > SoftwareNand2016Employees = GetEmployee.GetEmployeeBy(YearNandDepartment, employees);  
  53.         foreach(var employee in SoftwareNand2016Employees) {  
  54.             Console.WriteLine(employee.FirstName);  
  55.         }  
  56.         Console.WriteLine("\nList of Staff who are not in Software");  
  57.         NotSpecification NotDepartment = newNotSpecification(newEmployeeDepartmentSpecification("Software"));  
  58.         List < Employee > NotSoftwareEmployees = GetEmployee.GetEmployeeBy(NotDepartment, employees);  
  59.         foreach(var employee in NotSoftwareEmployees) {  
  60.             Console.WriteLine(employee.FirstName);  
  61.         }  
  62.         Console.ReadKey();  
  63.     }  
  64. }  

The picture below shows the expected output.

Output

I hope this article has been helpful. Please drop comments and feedback in the Comments section below. Also, follow me on C# Corner to know when my next article comes up.

References

  • https://en.wikipedia.org/wiki/Specification_pattern
  • https://matt.berther.io/2005/03/25/the-specification-pattern-a-primer/