Friday, January 31, 2014

Strategy pattern and higher-order functions?

As a practice, I'm implementing different design patterns in different languages.

I'm using the examples in the great Head First Design Patterns book as the basis for this implementations.
The book examples are written in Java, so I'll use other languages.

I've started with the Strategy pattern.
"The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it."
The first implementation is written in C++:
I modified the book example to make it shorter. In this case the Duck class has only one configurable behavior: quacking.

This behavior is injected through its constructor but can also be modified afterwards using a setter: changeHowToQuack.

Since C++ is statically typed like Java, we need to create a base type for all the different behaviors or strategies. In this case the QuackBehavior abstract class (this would work as a Java interface).

The Duck class has a field, howToQuack, which is a pointer to an object that implements the QuackBehavior interface. When Duck's quack() method is called, it delegates the quacking responsibility to its howToQuack collaborator.

This is one of the implementations of QuackBehavior:

You can find the rest of them in this Bitbucket repository: StrategyPatternExampleCpp.
This is an example of how this code can be used:

And this would be its result:
Quack!
Squeak!
<< Silence >>
Using this pattern we've encapsulated the quacking behavior and made it possible to change how the duck quacks without changing its code just by injecting new variants of the behavior. The code respects the Open-Closed Principle for the quack behavior because it's Open to extension (by creating new types of QuackBehavior) and Closed to variation (we don't need to change the Duck class).

This way of implementing the Strategy pattern in C++ or Java has to do with two facts:
  1. They are both statically typed languages. 
  2. Functions are not first-class citizens in any of them.
In languages like Ruby or Python we wouldn't need to use an interface like QuackBehavior. We'd just need to make the different behaviors (Quack, Squeak and MuteQuack) have a common interface: quack().

Moreover, since functions are first-class citizens in these languages, we wouldn't even need classes for the different types of behaviors. In this case, functions would suffice, as you can see in this other example of the Strategy pattern in Ruby (much less verbose than the C++ or Java ones):

The Strategy pattern relies on composition. I can't avoid thinking that it's a way to compose behavior that is very similar to what you do when you're using higher-order functions in functional programming.

This last example in Racket has the same functionality as the previous two:

Even though there are no objects here, you can notice some similarities.
It's also applying the Open/Closed principle because it's injecting a behavior as a function parameter, how-to-quack, that has several variations inside a higher-order function, quack, in order to change the function behavior without having to change its code. To get a new behavior, you just need to create a new type of quack function (a different quack behavior) and pass it to quack as a parameter.

Well I hope this makes you see the Strategy pattern from a little bit different perspective.

---------------------
PS: If you're interested in JavaScript, check this implementation of the Strategy pattern by Tomás Corral.

---------------------

Update:
A functional JavaScript version (very similar to Racket's one):

No comments:

Post a Comment