The Strategy Pattern explained using Java

Strategy pattern comes to the rescue!The Strategy PatternSo we will do this step by step.

Before we proceed, let me introduce you to a design principle:Identify the parts of your program that vary and separate them from what stays the same.

It is actually very straightforward — the principle states to separate and “encapsulate” anything that changes frequently so that all the code that changes lives in one place.

That way the code that changes will not have any effect on the rest of the program and our application is more flexible and robust.

In our case, the ‘bark ’and the ‘eat ’behavior can be taken out of the Dog class and can be encapsulated elsewhere.

We know that these behaviors vary across different dogs and they must get their own separate class.

We are going to create two set of classes apart from the Dog class, one for defining eating behavior and one for the barking behavior.

We will make use of interfaces to represent the behavior such as ‘EatBehavior ’ and ‘BarkBehavior ’ and the concrete behavior class will implement these interfaces.

So, the Dog class is not implementing the interface anymore.

We are creating separate classes whose sole job is to represent the specific behavior!This is what the EatBehavior interface looks likeinterface EatBehavior {public void eat();}And BarkBehaviorinterface BarkBehavior {public void bark();}All of the classes that represent these behaviors will implement the respective interface.

Concrete classes for BarkBehaviorpublic class PlayfulBark implements BarkBehavior { @override public void bark(){ System.

out.

println("Bark! Bark!"); }}public class Growl implements BarkBehavior { @override public void bark(){ System.

out.

println("This is a growl"); }public class MuteBark implements BarkBehavior { @override public void bark(){ System.

out.

println("This is a mute bark"); }Concrete classes for the EatBehaviorpublic class NormalDiet implements EatBehavior {@override public void eat(){ System.

out.

println("This is a normal diet"); }}public class ProteinDiet implements EatBehavior {@override public void eat(){ System.

out.

println("This is a protein diet"); }}Now while we make concrete implementations by subclassing the ‘Dog’ superclass, naturally we want to be able to assign the behaviors dynamically to the dogs’ instances.

After all, it was the inflexibility of the previous code that was causing the problem.

We can define setter methods on the Dog subclass that will allow us to set different behaviors at runtime.

That brings us to another design principle:Program to an interface and not an implementation.

What this means is that instead of using the concrete classes we use variables that are supertypes of those classes.

In other words, we use variables of type EatBehavior and BarkBehavior and assign these variables objects of classes that implement these behaviors.

That way, the Dog classes do not need to have any information about the actual object types of those variables!To make the concept clear here’s an example that differentiates the two ways — Consider an abstract Animal class that has two concrete implementations, Dog and Cat.

Programming to an implementation would be:Dog d = new Dog();d.

bark();Here’s what programming to an interface looks like:Animal animal = new Dog();animal.

animalSound();Here, we know that animal contains an instance of a ‘Dog’ but we can use this reference polymorphically everywhere else in our code.

All we care about is that the animal instance is able to respond to the animalSound() method and the appropriate method, depending on the object assigned, gets called.

That was a lot to take in.

Without further explanation let’s see what our ‘Dog’ superclass looks like now:public abstract class Dog {EatBehavior eatBehavior;BarkBehaviour barkBehavior;public Dog(){}public void doBark() { barkBehavior.

bark(); }public void doEat() {eatBehavior.

eat(); }}Pay close attention to the methods of this class.

The Dog class is now ‘delegating’ the task of eating and barking instead of implementing by itself or inheriting it(subclass).

In the doBark() method we simply call the bark() method on the object referenced by barkBehavior.

Now, we don’t care about the object’s actual type, we only care whether it knows how to bark!Now the moment of truth, let’s create a concrete Dog!public class Labrador extends Dog {public Labrador(){ barkBehavior = new PlayfulBark(); eatBehavior = new NormalDiet(); }public void display(){ System.

out.

println("I'm a playful Labrador"); } .

}What’s happening in the constructor of the Labrador class?.we are assigning the concrete instances to the supertype (remember the interface types are inherited from the Dog superclass).

Now, when we call doEat() on the Labrador instance, the responsibility is handed over to the ProteinDiet class and it executes the eat() method.

The Strategy Pattern in ActionAlright, let’s see this in action.

The time has come to run our dope Dog simulator program!public class DogSimulatorApp { public static void main(String[] args) { Dog lab = new Labrador(); lab.

doEat(); // Prints "This is a normal diet" lab.

doBark(); // "Bark!.Bark!" }}How can we make this program better?.By adding flexibility!.Let’s add setter methods on the Dog class to be able to swap behaviors at runtime.

Let’s add two more methods to the Dog superclass:public void setEatBehavior(EatBehavior eb){ eatBehavior = eb;}public void setBarkBehavior(BarkBehavior bb){ barkBehavior = bb;}Now we can modify our program and choose whatever behavior we like at runtime!public class DogSimulatorApp { public static void main(String[] args){ Dog lab = new Labrador(); lab.

doEat(); // This is a normal diet lab.

setEatBehavior(new ProteinDiet()); lab.

doEat(); // This is a protein dietlab.

doBark(); // Bark!.Bark!.}}Let’s look at the big picture:Class DiagramWe have the Dog superclass and the ‘Labrador’ class which is a subclass of Dog.

Then we have the family of algorithms (Behaviors) “encapsulated” with their respective behavior types.

Take a look at the formal definition that I gave at the beginning: the algorithms are nothing but the behavior interfaces.

Now they can be used not only in this program but other programs can also make use of it.

Notice the relationships between the classes in the diagram.

The IS-A and HAS-A relationships can be inferred from the diagram.

That’s it! I hope you have gotten a big picture overview of the Strategy pattern.

The Strategy pattern is extremely useful when you have certain behaviors in your app that change constantly.

This brings us to the end of the Java implementation.

Thank you so much for sticking with me so far! If you are interested to learn about the Kotlin version, stay tuned for the next post.

I talk about interesting language features and how we can reduce all of the above code in a single Kotlin file :).

. More details

Leave a Reply