首页 > 代码库 > How Scala killed the Strategy Pattern

How Scala killed the Strategy Pattern

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:

A UML diagram describing the Strategy Pattern

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:

  1. Holy crap, that’s an insane amount of boilerplate code.
  2. 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 * 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 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 Intparameters, 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:

  • Here on the O’Reilly website, and
  • Here on Amazon.com

I hope this tutorial has been helpful. All the best, Al.

tags: