Search code examples
design-patternsscalastrategy-pattern

Better alternative to Strategy pattern in Scala?


When I'm programming in Java (or a similar language), I often employ a simple version of the Strategy pattern, using interfaces and implementation classes, to provide runtime-selectable implementations of a particular concept in my code.

As a very contrived example, I might want to have the general concept of an Animal that can make a noise in my Java code, and want to be able to select the type of animal at runtime. So I would write code along these lines:

interface Animal {
    void makeNoise();
}

class Cat extends Animal {
    void makeNoise() { System.out.println("Meow"); }
}

class Dog extends Animal {
    void makeNoise() { System.out.println("Woof"); }
}

class AnimalContainer {
    Animal myAnimal;

    AnimalContainer(String whichOne) {
        if (whichOne.equals("Cat"))
            myAnimal = new Cat();
        else
            myAnimal = new Dog();
    }

    void doAnimalStuff() {
        ...
        // Time for the animal to make a noise
        myAnimal.makeNoise();
        ...
    }

Simple enough. Recently, though, I've been working on a project in Scala and I want to do the same thing. It seems easy enough to do this using traits, with something like this:

trait Animal {
    def makeNoise:Unit
}

class Cat extends Animal {
    override def makeNoise:Unit = println("Meow")
}

class AnimalContainer {
    val myAnimal:Animal = new Cat
    ...
}

However, this seems very Java-like and not very functional--not to mention that traits and interfaces aren't really the same thing. So I'm wondering if there's a more idiomatic way to implement the Strategy pattern--or something like it--in my Scala code so that I can select a concrete implementation of an abstract concept at runtime. Or is using traits the best way to achieve this?


Solution

  • You can do a variation on the cake pattern.

    trait Animal {
        def makenoise: Unit
    }
    
    trait Cat extends Animal {
        override def makeNoise { println("Meow") }
    }
    
    trait Dog extends Animal {
        override def makeNoise { println("Woof") }
    }
    
    class AnimalContaineer {
        self: Animal =>
    
        def doAnimalStuff {
             // ...
             makeNoise
             // ...
         }
    }
    
    object StrategyExample extends Application {
        val ex1 = new AnimalContainer with Dog
        val ex2 = new AnimalContainer with Cat
    
        ex1.doAnimalStuff
        ex2.doAnimalStuff
    }
    

    In terms of the strategy pattern, the self type on the strategy indicates it must be mixed with a specific implementation of some sort of algorithm.