Specification
The Specification pattern encapsulates business rules in objects, allowing for dynamic combinations and reuse. It’s a way to separate complex logic concerning data from the objects that hold that data. This enables you to define a variety of rules, combine them, and then apply these rules to objects without directly embedding the logic within the object itself.
This pattern is particularly useful in applications with complex validation or filtering requirements. It allows for greater flexibility and maintainability, as rules can be added, modified, or combined without altering the core classes. It’s a core pattern in Domain-Driven Design for enriching model objects with behavior and logic.
Usage
The Specification pattern is commonly used in:
- Data Validation: Defining rules for acceptable data formats, ranges, or dependencies. For example, ensuring an email address is valid or a password meets complexity requirements.
- Business Rules Engines: Implementing complex decision-making logic based on various criteria.
- Querying and Filtering: Constructing dynamic queries to retrieve data based on specific conditions. This is common in ORM frameworks and data access layers.
- Access Control: Determining whether a user has permission to perform a certain action based on their role and the resource they are trying to access.
Examples
-
Hibernate (Java ORM): Hibernate utilizes Specifications as part of its Criteria API. Users define criteria by creating
Predicateobjects representing constraints on entity properties. These predicates can be combined using logical operators likeANDandORto form complex queries. TheCriteriaobject essentially is a Specification, defining the conditions for data retrieval. -
Doctrine (PHP ORM): Similar to Hibernate, Doctrine allows building queries using a
Whereclause which essentially models a Specification. You can define conditions on entity attributes and combine them using logical operators, enabling flexible filtering of results. TheQueryBuilder’swhere()method accepts specifications, dynamically building up the query.
Specimens
15 implementationsThe Specification pattern is a functional approach to defining complex data validation or filtering rules. It encapsulates a condition as an object, allowing for reusable and composable logic. Instead of embedding validation directly within a class, Specifications define what criteria data must meet, separate from how the data is handled.
This Dart implementation uses a generic Specification class with a satisfies method that takes an object and returns a boolean. Concrete specifications are created by extending this class and overriding satisfies. The and, or, and not methods allow for combining specifications, promoting code reuse and readability. This approach aligns with Dart’s support for functional programming and encourages a declarative style.
// specification.dart
abstract class Specification<T> {
bool satisfies(T item);
Specification<T> and(Specification<T> other) {
return _AndSpecification(this, other);
}
Specification<T> or(Specification<T> other) {
return _OrSpecification(this, other);
}
Specification<T> not() {
return _NotSpecification(this);
}
}
class _AndSpecification<T> implements Specification<T> {
final Specification<T> first;
final Specification<T> second;
_AndSpecification(this.first, this.second);
@override
bool satisfies(T item) => first.satisfies(item) && second.satisfies(item);
}
class _OrSpecification<T> implements Specification<T> {
final Specification<T> first;
final Specification<T> second;
_OrSpecification(this.first, this.second);
@override
bool satisfies(T item) => first.satisfies(item) || second.satisfies(item);
}
class _NotSpecification<T> implements Specification<T> {
final Specification<T> spec;
_NotSpecification(this.spec);
@override
bool satisfies(T item) => !spec.satisfies(item);
}
class IsPositive extends Specification<int> {
@override
bool satisfies(int number) => number > 0;
}
class IsEven extends Specification<int> {
@override
bool satisfies(int number) => number % 2 == 0;
}
void main() {
final isPositiveAndEven = IsPositive().and(IsEven());
print(isPositiveAndEven.satisfies(4)); // true
print(isPositiveAndEven.satisfies(5)); // false
print(isPositiveAndEven.satisfies(-2)); // false
final isPositiveOrNegative = IsPositive().or(IsEven().not());
print(isPositiveOrNegative.satisfies(3)); // true
print(isPositiveOrNegative.satisfies(2)); // false
print(isPositiveOrNegative.satisfies(-1)); // true
}
The Specification pattern is a way to define complex business rules or constraints as objects. These objects encapsulate the logic for checking if a given data object meets the criteria. This promotes reusability, composability, and testability of rules.
The Scala code defines a Specification trait with a isSatisfiedBy method. Concrete specifications are created by extending this trait and implementing the isSatisfiedBy method to define the specific rule. The example demonstrates combining specifications using and and or to create more complex rules. Scala’s functional nature and support for traits make it a natural fit for this pattern, allowing for concise and composable rule definitions. Immutability is also leveraged for thread safety and predictable behavior.
trait Specification[T] {
def isSatisfiedBy(item: T): Boolean
def and(other: Specification[T]): Specification[T] =
new Specification[T] {
def isSatisfiedBy(item: T): Boolean =
Specification.this.isSatisfiedBy(item) && other.isSatisfiedBy(item)
}
def or(other: Specification[T]): Specification[T] =
new Specification[T] {
def isSatisfiedBy(item: T): Boolean =
Specification.this.isSatisfiedBy(item) || other.isSatisfiedBy(item)
}
}
case class Person(age: Int, name: String)
object SpecificationExamples {
def main(args: Array[String]): Unit = {
val adult = new Specification[Person] {
def isSatisfiedBy(person: Person): Boolean = person.age >= 18
}
val namedBob = new Specification[Person] {
def isSatisfiedBy(person: Person): Boolean = person.name == "Bob"
}
val bobAndAdult = adult and namedBob
val person1 = Person(20, "Bob")
val person2 = Person(15, "Bob")
val person3 = Person(20, "Alice")
println(s"Person 1 satisfies adult and Bob: ${bobAndAdult.isSatisfiedBy(person1)}") // true
println(s"Person 2 satisfies adult and Bob: ${bobAndAdult.isSatisfiedBy(person2)}") // false
println(s"Person 3 satisfies adult and Bob: ${bobAndAdult.isSatisfiedBy(person3)}") // false
val adultOrBob = adult or namedBob
println(s"Person 1 satisfies adult or Bob: ${adultOrBob.isSatisfiedBy(person1)}") // true
println(s"Person 2 satisfies adult or Bob: ${adultOrBob.isSatisfiedBy(person2)}") // true
println(s"Person 3 satisfies adult or Bob: ${adultOrBob.isSatisfiedBy(person3)}") // true
}
}
The Specification pattern is a behavioral design pattern that defines a way to represent complex conditions using boolean expressions. It allows you to decouple the logic that determines if something is valid from the object it’s being applied to. This promotes reusability and composability of validation rules.
The code defines an Item class and a Specification interface with a isSatisfiedBy() method. Concrete specifications like PriceSpecification and NameSpecification implement this interface to check specific item properties. These specifications can be combined using logical operators (and, or, not) to create more complex rules. The example demonstrates checking if an item is both expensive and has a valid name. This approach is idiomatic PHP due to its flexible type system and support for interfaces and object composition.
<?php
interface Specification
{
public function isSatisfiedBy(Item $item): bool;
}
class Item
{
public function __construct(
public string $name,
public float $price
) {}
}
class PriceSpecification implements Specification
{
public function __construct(public float $minPrice) {}
public function isSatisfiedBy(Item $item): bool
{
return $item->price >= $this->minPrice;
}
}
class NameSpecification implements Specification
{
public function isSatisfiedBy(Item $item): bool
{
return !empty($item->name);
}
}
class AndSpecification implements Specification
{
public function __construct(
private Specification $specification1,
private Specification $specification2
) {}
public function isSatisfiedBy(Item $item): bool
{
return $this->specification1->isSatisfiedBy($item) && $this->specification2->isSatisfiedBy($item);
}
}
// Usage
$item1 = new Item("Laptop", 1200.00);
$item2 = new Item("", 500.00);
$expensive = new PriceSpecification(1000.00);
$validName = new NameSpecification();
$combinedSpec = new AndSpecification($expensive, $validName);
if ($combinedSpec->isSatisfiedBy($item1)) {
echo "Item 1 is expensive and has a valid name.\n";
} else {
echo "Item 1 does not meet the criteria.\n";
}
if ($combinedSpec->isSatisfiedBy($item2)) {
echo "Item 2 is expensive and has a valid name.\n";
} else {
echo "Item 2 does not meet the criteria.\n";
}
?>
The Specification pattern allows you to define a set of rules (specifications) that an object must satisfy to be considered valid or to perform a specific action. Instead of scattering validation logic throughout your code, you encapsulate it within these reusable specification objects. This promotes cleaner, more maintainable code, especially when dealing with complex validation scenarios.
The Ruby code defines a Specification base class and a concrete EmailFormatSpecification. The EmailFormatSpecification checks if a given string is a valid email address using a regular expression. The specifies? method in the specification determines if an object meets the criteria. The example demonstrates how to use this specification to validate an email address, showcasing the pattern’s ability to encapsulate and reuse validation rules. This approach aligns with Ruby’s emphasis on object-oriented design and the principle of single responsibility.
# specification.rb
class Specification
def initialize(object)
@object = object
end
def specifies?
raise NotImplementedError, "Subclasses must implement the specifies? method"
end
private
attr_reader :object
end
class EmailFormatSpecification < Specification
def specifies?
!!(object =~ /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i)
end
end
# Example Usage
email = "test@example.com"
spec = EmailFormatSpecification.new(email)
if spec.specifies?
puts "#{email} is a valid email address."
else
puts "#{email} is not a valid email address."
end
email = "invalid-email"
spec = EmailFormatSpecification.new(email)
if spec.specifies?
puts "#{email} is a valid email address."
else
puts "#{email} is not a valid email address."
end
The Specification pattern decouples the construction of a complex object from its representation. It defines a class that represents the desired properties of an object, and separate “builder” classes that implement these specifications to create the object. This promotes flexibility and allows for creating different variations of an object based on different specifications without modifying the object’s core construction logic.
The Swift code defines a Specification protocol outlining the properties a valid object must have. A Person struct represents the object being built. Concrete specifications like YoungAdultSpecification and EmployedSpecification check for specific criteria. A PersonBuilder constructs the Person incrementally, and the SpecificationValidator ensures the built object meets the given specifications before finalizing it. This approach leverages Swift’s protocols and structs for a clean, type-safe implementation.
// Specification.swift
protocol Specification {
func isSatisfiedBy(_ candidate: Person) -> Bool
}
struct Person {
let name: String
let age: Int
let isEmployed: Bool
}
struct YoungAdultSpecification: Specification {
func isSatisfiedBy(_ candidate: Person) -> Bool {
return candidate.age >= 18 && candidate.age <= 30
}
}
struct EmployedSpecification: Specification {
func isSatisfiedBy(_ candidate: Person) -> Bool {
return candidate.isEmployed
}
}
class PersonBuilder {
private var name: String = ""
private var age: Int = 0
private var isEmployed: Bool = false
func withName(_ name: String) -> Self {
self.name = name
return self
}
func withAge(_ age: Int) -> Self {
self.age = age
return self
}
func withEmployment(_ isEmployed: Bool) -> Self {
self.isEmployed = isEmployed
return self
}
func build() -> Person {
return Person(name: name, age: age, isEmployed: isEmployed)
}
}
class SpecificationValidator {
static func validate(_ person: Person, against specifications: [Specification]) -> Bool {
return specifications.allSatisfy { spec in
spec.isSatisfiedBy(person)
}
}
}
// Example Usage
let builder = PersonBuilder()
.withName("Alice")
.withAge(25)
.withEmployment(true)
let person = builder.build()
let specifications: [Specification] = [YoungAdultSpecification(), EmployedSpecification()]
if SpecificationValidator.validate(person, against: specifications) {
print("Person is valid")
} else {
print("Person is invalid")
}
The Specification pattern is a behavioral pattern that defines a class for encapsulating business logic in a reusable way. It represents a condition that can be evaluated against an object to determine if it satisfies certain criteria. This promotes decoupling of the logic from the object itself, making the code more maintainable and testable.
The Kotlin example defines a Specification interface with a matches method. Concrete specifications like IsPositive and IsLessThan implement this interface to define specific criteria. A Filter function then uses these specifications to filter a list of integers, demonstrating how the pattern can be applied to data manipulation. This approach is idiomatic Kotlin due to its use of interfaces, functional programming concepts (like the matches method acting as a predicate), and concise syntax.
// Specification Pattern in Kotlin
interface Specification<T> {
fun matches(item: T): Boolean
}
class IsPositive : Specification<Int> {
override fun matches(item: Int): Boolean = item > 0
}
class IsLessThan(private val max: Int) : Specification<Int> {
override fun matches(item: Int): Boolean = item < max
}
// Combining Specifications (optional)
operator fun <T> Specification<T>.and(other: Specification<T>): Specification<T> {
return object : Specification<T> {
override fun matches(item: T): Boolean = this@and.matches(item) && other.matches(item)
}
}
// Example Usage
fun filterBySpecification(items: List<Int>, spec: Specification<Int>): List<Int> {
return items.filter { spec.matches(it) }
}
fun main() {
val numbers = listOf(1, -2, 3, 4, -5, 6)
val positiveNumbers = filterBySpecification(numbers, IsPositive())
println("Positive Numbers: $positiveNumbers") // Output: [1, 3, 4, 6]
val lessThanFive = filterBySpecification(numbers, IsLessThan(5))
println("Less than Five: $lessThanFive") // Output: [1, -2, 3, 4, -5]
val positiveAndLessThanFive = filterBySpecification(numbers, IsPositive().and(IsLessThan(5)))
println("Positive and Less than Five: $positiveAndLessThanFive") // Output: [1, 3, 4]
}
The Specification pattern is a functional approach to defining complex logic through composable predicates. Instead of embedding conditions directly within code, it encapsulates them as objects (in this case, structs with a call operator) that can be combined using logical operators like and, or, and not. This promotes code reusability, readability, and easier testing.
The Rust implementation uses structs to represent the specifications and implements the Fn trait to make them callable as functions that return a boolean. The and, or, and not functions return new specification instances, composing the logic. This leverages Rust’s strong type system and functional capabilities for a concise and type-safe solution. The use of closures for the specification logic is idiomatic Rust.
// Specification pattern in Rust
#[derive(Debug)]
struct Specification<T> {
predicate: Box<dyn Fn(&T) -> bool>,
}
impl<T> Specification<T> {
fn new(predicate: impl Fn(&T) -> bool + 'static) -> Self {
Specification {
predicate: Box::new(predicate),
}
}
fn and<U>(self, other: Specification<U>) -> Specification<U>
where
T: AsRef<U>,
{
Specification::new(move |item: &U| self.predicate(item.as_ref()) && other.predicate(item))
}
fn or<U>(self, other: Specification<U>) -> Specification<U>
where
T: AsRef<U>,
{
Specification::new(move |item: &U| self.predicate(item.as_ref()) || other.predicate(item))
}
fn not(self) -> Specification<T> {
Specification::new(move |item: &T| !self.predicate(item))
}
fn is_satisfied_by(&self, item: &T) -> bool {
(self.predicate)(item)
}
}
fn main() {
struct Person {
name: String,
age: u32,
}
let is_adult = Specification::new(|p: &Person| p.age >= 18);
let is_named_alice = Specification::new(|p: &Person| p.name == "Alice");
let is_adult_alice = is_adult.and(is_named_alice);
let person1 = Person { name: "Alice".to_string(), age: 25 };
let person2 = Person { name: "Bob".to_string(), age: 15 };
println!("Person 1 is adult and named Alice: {}", is_adult_alice.is_satisfied_by(&person1)); // true
println!("Person 2 is adult and named Alice: {}", is_adult_alice.is_satisfied_by(&person2)); // false
let is_not_adult = is_adult.not();
println!("Person 1 is not an adult: {}", is_not_adult.is_satisfied_by(&person1)); // false
println!("Person 2 is not an adult: {}", is_not_adult.is_satisfied_by(&person2)); // true
}
The Specification pattern is a functional design pattern that encapsulates a business rule or condition into an object. This allows for easy composition of complex rules by combining simpler specifications using logical operators (AND, OR, NOT). The code defines a Specification type with a Satisfies method. Concrete specifications implement this interface to define specific criteria. The example demonstrates checking if a number is positive, even, and within a range, combining these with And, Or, and Not functions. This approach is idiomatic Go as it leverages interfaces for flexible behavior and utilizes function composition, a common functional programming technique, to build complex logic in a readable and maintainable way.
package main
import "fmt"
// Specification interface defines the contract for checking a condition.
type Specification interface {
Satisfies(value int) bool
}
// IsPositive checks if a number is positive.
type IsPositive struct{}
func (s IsPositive) Satisfies(value int) bool {
return value > 0
}
// IsEven checks if a number is even.
type IsEven struct{}
func (s IsEven) Satisfies(value int) bool {
return value%2 == 0
}
// InRange checks if a number is within a specified range.
type InRange struct {
Low int
High int
}
func (s InRange) Satisfies(value int) bool {
return value >= s.Low && value <= s.High
}
// And combines two specifications with a logical AND.
func And(spec1, spec2 Specification) Specification {
return &AndSpec{spec1, spec2}
}
type AndSpec struct {
spec1 Specification
spec2 Specification
}
func (s *AndSpec) Satisfies(value int) bool {
return s.spec1.Satisfies(value) && s.spec2.Satisfies(value)
}
// Or combines two specifications with a logical OR.
func Or(spec1, spec2 Specification) Specification {
return &OrSpec{spec1, spec2}
}
type OrSpec struct {
spec1 Specification
spec2 Specification
}
func (s *OrSpec) Satisfies(value int) bool {
return s.spec1.Satisfies(value) || s.spec2.Satisfies(value)
}
// Not negates a specification.
func Not(spec Specification) Specification {
return &NotSpec{spec}
}
type NotSpec struct {
spec Specification
}
func (s *NotSpec) Satisfies(value int) bool {
return !s.spec.Satisfies(value)
}
func main() {
positiveAndEven := And(IsPositive{}, IsEven{})
inRange1to10 := InRange{Low: 1, High: 10}
positiveAndEvenInRange := And(positiveAndEven, inRange1to10)
fmt.Println(positiveAndEvenInRange.Satisfies(4)) // true
fmt.Println(positiveAndEvenInRange.Satisfies(5)) // false
fmt.Println(positiveAndEvenInRange.Satisfies(-2)) // false
fmt.Println(Not(IsPositive{}).Satisfies(-1)) // true
}
The Specification pattern is a functional approach to defining complex data validation or filtering rules. It encapsulates these rules as objects, allowing for dynamic combination and reuse. This implementation uses function pointers to represent the specifications, making it lightweight and idiomatic for C. The spec_and, spec_or, and spec_not functions combine specifications, while the spec_match function applies a specification to a given data item. This avoids the need for complex object hierarchies common in OOP languages, fitting C’s procedural nature. The example demonstrates checking if a number is positive, even, and within a range.
#include <stdio.h>
#include <stdbool.h>
// Specification type: a function pointer that takes an int and returns a bool
typedef bool (*Specification)(int);
// Define individual specifications
bool is_positive(int value) {
return value > 0;
}
bool is_even(int value) {
return value % 2 == 0;
}
bool is_in_range(int value) {
return value >= 10 && value <= 20;
}
// Combine specifications
Specification spec_and(Specification spec1, Specification spec2) {
return (Specification)(^(int value) { return spec1(value) && spec2(value); });
}
Specification spec_or(Specification spec1, Specification spec2) {
return (Specification)(^(int value) { return spec1(value) || spec2(value); });
}
Specification spec_not(Specification spec) {
return (Specification)(^(int value) { return !spec(value); });
}
// Apply a specification to a value
bool spec_match(Specification spec, int value) {
return spec(value);
}
int main() {
// Create a combined specification: positive AND even AND in range 10-20
Specification combined_spec = spec_and(is_positive, spec_and(is_even, is_in_range));
int test_values[] = {5, 12, 15, -8, 22};
int num_values = sizeof(test_values) / sizeof(test_values[0]);
for (int i = 0; i < num_values; i++) {
printf("%d matches: %s\n", test_values[i], spec_match(combined_spec, test_values[i]) ? "true" : "false");
}
return 0;
}
The Specification pattern is a functional approach to defining and validating complex conditions or constraints on objects. Instead of embedding these conditions directly within the object’s methods, it encapsulates them in separate, reusable specification objects. These specifications can then be combined using logical operators (AND, OR, NOT) to create even more complex rules.
This C++ implementation uses function objects (functors) to represent specifications. Each specification is a class with an overloaded operator(), which takes the object to be tested and returns a boolean. The AndSpec, OrSpec, and NotSpec classes combine specifications using logical operations. This approach is idiomatic C++ as it leverages function objects for flexible and composable behavior, avoiding inheritance-heavy solutions often seen in other languages. The example demonstrates specifying criteria for a Person object based on age and name.
#include <iostream>
#include <string>
#include <functional>
class Person {
public:
Person(const std::string& name, int age) : name_(name), age_(age) {}
const std::string& getName() const { return name_; }
int getAge() const { return age_; }
private:
std::string name_;
int age_;
};
// Specification base class (using std::function for flexibility)
class Specification {
public:
virtual bool satisfiedBy(const Person& person) const = 0;
};
// Concrete specification: Age is greater than a threshold
class AgeSpecification : public Specification {
public:
AgeSpecification(int age) : age_(age) {}
bool satisfiedBy(const Person& person) const override {
return person.getAge() > age_;
}
private:
int age_;
};
// Concrete specification: Name contains a substring
class NameSpecification : public Specification {
public:
NameSpecification(const std::string& substring) : substring_(substring) {}
bool satisfiedBy(const Person& person) const override {
return person.getName().find(substring_) != std::string::npos;
}
private:
std::string substring_;
};
// Composite specification: AND
class AndSpec : public Specification {
public:
AndSpec(const Specification& spec1, const Specification& spec2) : spec1_(spec1), spec2_(spec2) {}
bool satisfiedBy(const Person& person) const override {
return spec1_.satisfiedBy(person) && spec2_.satisfiedBy(person);
}
private:
const Specification& spec1_;
const Specification& spec2_;
};
// Composite specification: OR
class OrSpec : public Specification {
public:
OrSpec(const Specification& spec1, const Specification& spec2) : spec1_(spec1), spec2_(spec2) {}
bool satisfiedBy(const Person& person) const override {
return spec1_.satisfiedBy(person) || spec2_.satisfiedBy(person);
}
private:
const Specification& spec1_;
const Specification& spec2_;
};
// Composite specification: NOT
class NotSpec : public Specification {
public:
NotSpec(const Specification& spec) : spec_(spec) {}
bool satisfiedBy(const Person& person) const override {
return !spec_.satisfiedBy(person);
}
private:
const Specification& spec_;
};
int main() {
Person person1("Alice Smith", 30);
Person person2("Bob Johnson", 25);
Person person3("Charlie Brown", 18);
AgeSpecification ageSpec(28);
NameSpecification nameSpec("Smith");
AndSpec combinedSpec(ageSpec, nameSpec);
std::cout << "Person 1 satisfies combined spec: " << combinedSpec.satisfiedBy(person1) << std::endl;
std::cout << "Person 2 satisfies combined spec: " << combinedSpec.satisfiedBy(person2) << std::endl;
std::cout << "Person 3 satisfies combined spec: " << combinedSpec.satisfiedBy(person3) << std::endl;
return 0;
}
The Specification pattern is a behavioral design pattern that encapsulates a set of criteria (a “specification”) as an object. This allows you to decouple complex filtering logic from the data it operates on, making code more maintainable and reusable. The code defines an ISpecification interface with methods for IsSatisfiedBy (checking if an object meets the criteria) and Combine (composing specifications). A concrete AgeSpecification implements this to check if a person’s age is within a range. C#’s use of interfaces and LINQ makes this pattern a natural fit, promoting loose coupling and expressive querying.
// Specification Pattern in C#
public interface ISpecification<T>
{
bool IsSatisfiedBy(T item);
ISpecification<T> Combine(ISpecification<T> other);
}
public class AgeSpecification : ISpecification<int>
{
private readonly int _minAge;
private readonly int _maxAge;
public AgeSpecification(int minAge, int maxAge)
{
_minAge = minAge;
_maxAge = maxAge;
}
public bool IsSatisfiedBy(int age)
{
return age >= _minAge && age <= _maxAge;
}
public ISpecification<int> Combine(ISpecification<int> other)
{
return new AndSpecification<int>(this, other);
}
}
public class AndSpecification<T> : ISpecification<T>
{
private readonly ISpecification<T> _first;
private readonly ISpecification<T> _second;
public AndSpecification(ISpecification<T> first, ISpecification<T> second)
{
_first = first;
_second = second;
}
public bool IsSatisfiedBy(T item)
{
return _first.IsSatisfiedBy(item) && _second.IsSatisfiedBy(item);
}
public ISpecification<T> Combine(ISpecification<T> other)
{
return new AndSpecification<T>(this, other);
}
}
public class Example
{
public static void Main(string[] args)
{
var ageSpec = new AgeSpecification(18, 65);
// Example usage with a list of ages
List<int> ages = new List<int> { 15, 25, 70, 30, 60 };
var filteredAges = ages.Where(ageSpec.IsSatisfiedBy).ToList();
Console.WriteLine(string.Join(", ", filteredAges)); // Output: 25, 30, 60
}
}
The Specification pattern is a functional approach to encapsulating business logic and rules. It allows you to define a boolean predicate (a “specification”) as a first-class object, which can then be reused and combined with other specifications using logical operators (AND, OR, NOT). This promotes code reusability, testability, and separation of concerns by keeping filtering and validation logic separate from the data it operates on.
The TypeScript implementation defines a Specification interface with a isSatisfiedBy method. Concrete specifications implement this interface to define specific criteria. The code demonstrates combining specifications using and, or, and not to create more complex rules. Using interfaces and functional composition aligns well with TypeScript’s type system and encourages immutable data handling, making the code cleaner and easier to reason about.
// Specification Interface
interface Specification<T> {
isSatisfiedBy(item: T): boolean;
and(other: Specification<T>): Specification<T>;
or(other: Specification<T>): Specification<T>;
not(): Specification<T>;
}
// Concrete Specification: IsEven
class IsEven implements Specification<number> {
isSatisfiedBy(item: number): boolean {
return item % 2 === 0;
}
and(other: Specification<number>): Specification<number> {
return new AndSpecification(this, other);
}
or(other: Specification<number>): Specification<number> {
return new OrSpecification(this, other);
}
not(): Specification<number> {
return new NotSpecification(this);
}
}
// Concrete Specification: IsPositive
class IsPositive implements Specification<number> {
isSatisfiedBy(item: number): boolean {
return item > 0;
}
and(other: Specification<number>): Specification<number> {
return new AndSpecification(this, other);
}
or(other: Specification<number>): Specification<number> {
return new OrSpecification(this, other);
}
not(): Specification<number> {
return new NotSpecification(this);
}
}
// Composite Specifications
class AndSpecification<T> implements Specification<T> {
constructor(private spec1: Specification<T>, private spec2: Specification<T>) {}
isSatisfiedBy(item: T): boolean {
return this.spec1.isSatisfiedBy(item) && this.spec2.isSatisfiedBy(item);
}
and(other: Specification<T>): Specification<T> {
return new AndSpecification(this, other);
}
or(other: Specification<T>): Specification<T> {
return new OrSpecification(this, other);
}
not(): Specification<T> {
return new NotSpecification(this);
}
}
class OrSpecification<T> implements Specification<T> {
constructor(private spec1: Specification<T>, private spec2: Specification<T>) {}
isSatisfiedBy(item: T): boolean {
return this.spec1.isSatisfiedBy(item) || this.spec2.isSatisfiedBy(item);
}
and(other: Specification<T>): Specification<T> {
return new AndSpecification(this, other);
}
or(other: Specification<T>): Specification<T> {
return new OrSpecification(this, other);
}
not(): Specification<T> {
return new NotSpecification(this);
}
}
class NotSpecification<T> implements Specification<T> {
constructor(private spec: Specification<T>) {}
isSatisfiedBy(item: T): boolean {
return !this.spec.isSatisfiedBy(item);
}
and(other: Specification<T>): Specification<T> {
return new AndSpecification(this, other);
}
or(other: Specification<T>): Specification<T> {
return new OrSpecification(this, other);
}
not(): Specification<T> {
return new NotSpecification(this.spec);
}
}
// Example Usage
const evenAndPositive = new IsEven().and(new IsPositive());
console.log(evenAndPositive.isSatisfiedBy(4)); // true
console.log(evenAndPositive.isSatisfiedBy(2)); // true
console.log(evenAndPositive.isSatisfiedBy(-2)); // false
console.log(evenAndPositive.isSatisfiedBy(3)); // false
const evenOrPositive = new IsEven().or(new IsPositive());
console.log(evenOrPositive.isSatisfiedBy(4)); // true
console.log(evenOrPositive.isSatisfiedBy(3)); // true
console.log(evenOrPositive.isSatisfiedBy(-2)); // false
console.log(evenOrPositive.isSatisfiedBy(-1)); // false
const notEven = new IsEven().not();
console.log(notEven.isSatisfiedBy(3)); // true
console.log(notEven.isSatisfiedBy(4)); // false
The Specification pattern is a functional technique for encapsulating business logic into reusable, composable objects. Instead of embedding conditions directly within code, it defines a Specification object that represents a declarative criteria. This allows for dynamic querying and validation, and simplifies complex conditional logic.
The code defines a base Specification class with a isSatisfiedBy method. Concrete specifications (e.g., IsEven, GreaterThan) inherit from this base and implement the criteria. Specifications can be combined using and, or, and not to create more complex rules. This approach is idiomatic JavaScript due to its reliance on object composition and first-class functions, promoting a more declarative and testable style.
// Specification.js
class Specification {
constructor(callback) {
this.callback = callback;
}
isSatisfiedBy(candidate) {
return this.callback(candidate);
}
and(other) {
return new Specification(candidate => this.isSatisfiedBy(candidate) && other.isSatisfiedBy(candidate));
}
or(other) {
return new Specification(candidate => this.isSatisfiedBy(candidate) || other.isSatisfiedBy(candidate));
}
not() {
return new Specification(candidate => !this.isSatisfiedBy(candidate));
}
}
class IsEven extends Specification {
constructor() {
super(x => x % 2 === 0);
}
}
class GreaterThan extends Specification {
constructor(threshold) {
super(x => x > threshold);
}
}
// Example Usage:
const isEven = new IsEven();
const greaterThanTen = new GreaterThan(10);
const combinedSpec = isEven.and(greaterThanTen);
console.log(combinedSpec.isSatisfiedBy(12)); // true
console.log(combinedSpec.isSatisfiedBy(11)); // false
console.log(combinedSpec.isSatisfiedBy(8)); // false
const notEven = isEven.not();
console.log(notEven.isSatisfiedBy(7)); // true
console.log(notEven.isSatisfiedBy(4)); // false
The Specification pattern is a powerful way to encapsulate complex business rules into reusable objects. It allows you to define a predicate (a boolean expression) that can be applied to objects to determine if they satisfy certain criteria without tightly coupling the criteria to the object itself. This promotes flexibility and maintainability.
The Python implementation uses classes to define the specification and concrete specifications for specific criteria. The Specification class acts as an interface, and concrete specifications inherit from it, implementing the is_satisfied_by method. The code demonstrates checking if a product is both featured and within a price range. Python’s duck typing and first-class functions make this pattern a natural fit, avoiding the need for explicit interfaces in many cases.
from dataclasses import dataclass
from typing import TypeVar, Generic
T = TypeVar('T')
@dataclass
class Product:
name: str
price: float
is_featured: bool
class Specification(Generic[T]):
def is_satisfied_by(self, obj: T) -> bool:
raise NotImplementedError
class FeaturedProduct(Specification[Product]):
def is_satisfied_by(self, product: Product) -> bool:
return product.is_featured
class PriceLessThan(Specification[Product]):
def __init__(self, max_price: float):
self.max_price = max_price
def is_satisfied_by(self, product: Product) -> bool:
return product.price < self.max_price
class AndSpecification(Specification[Product]):
def __init__(self, spec1: Specification[Product], spec2: Specification[Product]):
self.spec1 = spec1
self.spec2 = spec2
def is_satisfied_by(self, product: Product) -> bool:
return self.spec1.is_satisfied_by(product) and self.spec2.is_satisfied_by(product)
# Example Usage
products = [
Product("Laptop", 1200.0, True),
Product("Mouse", 25.0, False),
Product("Keyboard", 75.0, True),
Product("Monitor", 300.0, False)
]
featured_and_cheap = AndSpecification(FeaturedProduct(), PriceLessThan(500.0))
for product in products:
if featured_and_cheap.is_satisfied_by(product):
print(f"{product.name} is featured and costs less than $500")
The Specification pattern is a functional construct that encapsulates business logic into boolean expressions. It allows you to define a complex selection criteria in a declarative way, separating the “what” from the “how” of the selection. This promotes reusability and composability of rules.
The Java implementation uses an interface Specification defining a test() method for evaluating a condition. Concrete specifications implement this interface, and can be combined using and(), or(), and not() methods to create more complex criteria. The example demonstrates filtering a list of Person objects based on age and name, showcasing how specifications can be chained and reused. This approach aligns with Java’s functional interfaces and promotes a more readable and maintainable codebase compared to deeply nested if statements.
import java.util.List;
import java.util.stream.Collectors;
interface Specification<T> {
boolean test(T t);
default Specification<T> and(Specification<T> other) {
return t -> test(t) && other.test(t);
}
default Specification<T> or(Specification<T> other) {
return t -> test(t) || other.test(t);
}
default Specification<T> not() {
return t -> !test(t);
}
}
class AgeSpecification implements Specification<Person> {
private final int age;
public AgeSpecification(int age) {
this.age = age;
}
@Override
public boolean test(Person person) {
return person.getAge() >= age;
}
}
class NameSpecification implements Specification<Person> {
private final String name;
public NameSpecification(String name) {
this.name = name;
}
@Override
public boolean test(Person person) {
return person.getName().contains(name);
}
}
class Person {
private final String name;
private final int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class SpecificationExample {
public static void main(String[] args) {
List<Person> people = List.of(
new Person("Alice", 30),
new Person("Bob", 25),
new Person("Charlie", 35),
new Person("David", 20)
);
Specification<Person> youngAndNamed = new AgeSpecification(25)
.and(new NameSpecification("a"));
List<Person> filteredPeople = people.stream()
.filter(youngAndNamed::test)
.collect(Collectors.toList());
System.out.println(filteredPeople);
}
}