loading...

Daily Challenge #246 - Readable Specification Pattern

thepracticaldev profile image dev.to staff ・2 min read

Specification pattern is one of the OOP design patterns. Its main use is combining simple rules into more complex ones for data filtering. For example, given a bar menu you could check whether a drink is a non-alcoholic cocktail using the following code:

drink = # ...

is_cocktail = IsCocktail()

is_alcoholic = IncludesAlcohol()

is_non_alcoholic = Not(is_alcoholic)

is_non_alcoholic_cocktail = And(is_cocktail, is_non_alcoholic)

is_non_alcoholic_cocktail.is_satisfied_by(drink)

But, this code is terrible! We have to create lots of unnecessary variables, and if we were to write this specification in one line, the result would be getting more and more unreadable as its complexity grows:

drink = # ...

And(IsCocktail(), Not(IncludesAlcohol())).is_satisfied_by(drink)

You don't want to write such code. Instead, you should implement a much more beautiful specification pattern by:

  • using the &, |, ~ operators instead of creating superfluous And, Or, Not classes
  • getting rid of those annoying class instantiations
  • calling the specification directly without any is_satisfied_by methods

Make something like this instead:

drink = # 
...(IsCocktail & ~IncludesAlcohol)(drink)

To do so you have to create a class Specification which will be inherited by other classes (like aforementioned IsCocktail or IncludesAlcohol), and will allow us to do this magic.

Note: whenever a class inheriting from Specification (e.g. IsCocktail) or a complex specification (e.g. IsCocktail & ~IncludesAlcohol) is called, instead of constructing and initializing a truthy/falsey instance, True/False must be returned.

Try it out and tell us what you've made!


This challenge comes from FArekkusu on CodeWars. Thank you to CodeWars, who has licensed redistribution of this challenge under the 2-Clause BSD License!

Want to propose a challenge idea for a future post? Email yo+challenge@dev.to with your suggestions!

Posted on by:

thepracticaldev profile

dev.to staff

@thepracticaldev

The hardworking team behind dev.to ❤️

Discussion

markdown guide
 

Part of the fun of these challenges is abusing language features. Let's do this for JS. I'll be using other predicates though as the cocktail and alcohol examples are not fleshed out:

class Even {}
Object.defineProperty(Even, Symbol.hasInstance, { value: n => n % 2 == 0 })

class SmallNatural {}
Object.defineProperty(SmallNatural, Symbol.hasInstance, { value: n => n > 0 && n < 10})

class SmallEven {}
Object.defineProperty(
  SmallEven, 
  Symbol.hasInstance, 
  { value: n => n instanceof Even && n instanceof SmallNatural}
)

console.log(`1 instanceof Even: ${1 instanceof Even}`)
// false
console.log(`2 instanceof Even: ${2 instanceof Even}`)
// true
console.log(`1 instanceof SmallNatural: ${1 instanceof SmallNatural}`)
// true
console.log(`101 instanceof SmallNatural: ${101 instanceof SmallNatural}`)
// false
console.log(`2 instanceof SmallEven: ${2 instanceof SmallEven}`)
// true
console.log(`3 instanceof SmallEven: ${3 instanceof SmallEven}`)
// false
console.log(`12 instanceof SmallEven: ${12 instanceof SmallEven}`)
// false

This is obviously not a great solution to the actual problem, but it's fun to explore how far you can take something.