How Scala killed the Strategy Pattern
By Alvin Alexander. Last updated: Mar 23, 2014
table of contents [hide]
- The
OOP Strategy Pattern - Two
immediate thoughts - How
Scala killed the Strategy Pattern - Understanding
the ‘execute’ method - Dude,
you used ‘method’ and ‘function’ interchangeably - What
about those other OOP design patterns? - The
Scala Cookbook
Summary: Simple functional programming techniques in Scala make certain OOP
design patterns, such as the Strategy Pattern, obsolete.
The OOP Strategy Pattern
Wikipedia describes the Strategy Pattern with this UML
diagram:
It’s further defined by the following Java code. But fear not, you don’t need
to read all the code, at least not yet; just note how long it
is as you scroll through it:
//___WIKIPEDIA CODE STARTS___/** The classes that implement a concrete strategy should implement this. */
* The Context class uses this to call the concrete strategy. */
interface Strategy {
int execute(int a, int b);
};/** Implements the algorithm using the strategy interface */
class Add implements Strategy {
public int execute(int a, int b) {
System.out.println("Called Add‘s execute()");
return a + b; // Do an addition with a and b
}
};class Subtract implements Strategy {
public int execute(int a, int b) {
System.out.println("Called Subtract‘s execute()");
return a - b; // Do a subtraction with a and b
}
};class Multiply implements Strategy {
public int execute(int a, int b) {
System.out.println("Called Multiply‘s execute()");
return a * b; // Do a multiplication with a and b
}
};// Configured with a ConcreteStrategy object and maintains
// a reference to a Strategy object
class Context {
private Strategy strategy;public Context(Strategy strategy) {
this.strategy = strategy;
}public int executeStrategy(int a, int b) {
return this.strategy.execute(a, b);
}
};/** Tests the pattern */
class StrategyExample {
public static void main(String[] args) {
Context context;// Three contexts following different strategies
context = new Context(new Add());
int resultA = context.executeStrategy(3,4);context = new Context(new Subtract());
int resultB = context.executeStrategy(3,4);context = new Context(new Multiply());
int resultC = context.executeStrategy(3,4);System.out.println("Result A : " + resultA );
System.out.println("Result B : " + resultB );
System.out.println("Result C : " + resultC );
}
};//___WIKIPEDIA CODE ENDS___
Two immediate thoughts
When I see code like this these days I think two things:
- Holy crap, that’s an insane amount of boilerplate code.
- I laugh, because I wrote Java code like that for what, almost 15 years?
Shoot, I even taught UML classes.
I knew that code was insane, but perhaps because my background is in
aerospace engineering and not computer science, I didn’t know why.
Then I learned Scala.
How Scala killed the Strategy
Pattern
In contrast, Scala code tends to be much more concise. In this particular
case, because you can pass algorithms around just like you pass objects around,
there is no need for the Strategy Pattern, and you can write the code like this
instead:
object DeathToStrategy extends App {def add(a: Int, b: Int) = a + b
def subtract(a: Int, b: Int) = a - b
def multiply(a: Int, b: Int) = a * bdef execute(callback:(Int, Int) => Int, x: Int, y: Int) = callback(x, y)
println("Add: " + execute(add, 3, 4))
println("Subtract: " + execute(subtract, 3, 4))
println("Multiply: " + execute(multiply, 3, 4))}
That’s it, that’s all; 50+ lines of boilerplate code reduced to ~12, and it’s
still easy to read.
The only “secret sauce” to this recipe is knowing:
- In Scala you can pass functions around just like other objects.
- How to define the signature for a method
likeexecute
. - How to pass a function and variables into a method
likeexecute
.
If you’d like more information on how that code works, or on the difference
between a method and afunction in Scala, read
on; otherwise, move on and have a great day.
Understanding the ‘execute’
method
In this example I told the execute
method that its
first parameter is a function that takes two Int
parameters,
and returns an Int
, like this:
callback:(Int, Int) => Int
The two Int
parameters the function accepts are
enclosed in the parentheses, and the Int
that is returned
by the function is shown on the right side of
the =>
symbol.
If you’re not used to functional programming in Scala, code like that may be
new, but as you can see, it’s pretty easy. It’s a little like a Java interface;
you’re defining the template for this method parameter, i.e., what this
parameter needs to look like when you call this method.
After defining that function as the first parameter of
the execute
method, I declared that its second and third
parameters are Int
values, like this:
x: Int, y: Int
That’s just the normal definition of Int
parameters in
Scala. In Java that would look like this:
int x, int y
Finally, I told the execute
method that when it’s
called it should run the callback
function it was given
with the two parameters it was given:
callback(x, y)
Putting these snippets together leads to the code I showed earlier:
def execute(callback:(Int, Int) => Int, x: Int, y: Int) = callback(x, y)
Note that this is how I wrote my Scala code when I first started learning
Scala; the “callback” name helped reinforce what I was doing. These days I just
use the letter f
instead
of callback
:
def execute(f:(Int, Int) => Int, x: Int, y: Int) = f(x, y)
In my mind I now know that “f” means “function”, and I find this more
readable than using the name “callback”.
Dude, you used
‘method’ and ‘function’ interchangeably
In this article I used the terms “method” and “function” interchangeably,
because in most situations in Scala they are interchangeable.
As a quick example of this, you saw that I defined
the add
method like this:
def add(a: Int, b: Int) = a + b
I can tell that this is a method because I defined it with
the def
keyword. All methods begin with that keyword.
I could have defined it as a function if I wanted to. The
syntax for a function looks like this:
val add = (x: Int, y: Int) => x + y
After defining it as a function, I can use it just like I did earlier:
println("Add: " + execute(add, 3, 4))
How awesome is that?
In your own code you can define algorithms as methods or functions, however
you like. I find that methods are slightly easier to read, but functions are
cool because you define them as val
, which reinforces the
notion that they are just values you can pass around, just like any other
object.
This is a beauty of Scala: You can use both. If you prefer one vs. the other,
awesome, use it.
What about those other OOP
design patterns?
Alas, Scala has killed not only the
Strategy design pattern, but other design patterns as well. (I wrote this
series on Design Patterns six years ago.) I could write more, and maybe
one day I will, but if you start using Scala and the techniques shown in this
article, you’ll find this out very quickly for yourself.
The Scala Cookbook
This tutorial is sponsored by the Scala Cookbook, which I wrote
and released in late 2013:
You can find the Scala Cookbook at these locations:
I hope this tutorial has been helpful. All the best, Al.
tags:
How Scala killed the Strategy Pattern,布布扣,bubuko.com