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:
(That image originally comes from mcdonaldland.info.)
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 OOP and 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 * b def 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 like
execute
. - How to pass a function and variables into a method like
execute
.
If you’d like more information on how that code works, or on the difference between a method and a function 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))
this post is sponsored by my books: | |||
#1 New Release |
FP Best Seller |
Learn Scala 3 |
Learn FP Fast |
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.