Skip to main content

Design Pattern: Strategy Pattern

Free2015-03-06#Design_Pattern#策略模式#Strategy Pattern

Design patterns are theoretical knowledge extracted from experience in optimizing code structure. Applying mature design patterns can enhance code reusability, extensibility, and maintainability. Among them, the Strategy Pattern is one of the most fundamental design patterns. Simply put, the Strategy Pattern encapsulates replaceable algorithm steps into individual algorithm families for dynamic selection at runtime.

no_mkd

I. What is the Strategy Pattern?

Literally speaking, the Strategy Pattern is a design pattern that applies a certain “strategy”, and this “strategy” is: encapsulate the changing parts.

Actually, this understanding is incorrect. The example is not entirely wrong, but the understanding of this pattern has deviations. Modified content has been appended at the end of this article, click here to jump>>

II. An Example

Assuming we need to use classes to describe Dog. First, all Dogs have appearance (such as Color) and behaviors (such as Run), so we naturally define a base class Dog like this:

public abstract class Dog {
	public abstract void display();//Display Dog's appearance
	public abstract void run();//Define Dog's Run behavior
}

Other Dog concrete classes just need to inherit from the Dog base class. Soon, a requirement comes: there is a Red Dog, it's a pet dog, runs slowly, and barks quietly. So we define the RedDog class:

public class RedDog {
	 @override
	public void display(){
		System.out.println("this is a red dog.");
	}
	 @override
	public void run(){
		System.out.println("it's running slowly.");
	}
}

Then, a batch of requirements comes: Black Dog, Gray Dog, Pink Dog, BlackGrayDog, PinkGrayDog... a total of 100 Dogs. Except for different Colors, the Run speeds are also different. So we define 100 classes to represent 100 different types of Dogs, each class implementing the Run method and Display method.

Hmm~, look at our class diagram. Yes, this BigBang is our class diagram. It seems reasonable. There are 100 types of Dogs, so of course there would be 100 classes. Moreover, concrete Dog classes should naturally inherit from the Dog base class, right? Yes, it seems so, and this doesn't seem to have any problems, at least nothing inappropriate can be seen for now...

A new requirement comes: we find that Dog can not only Run, but also Bark. Naturally, we think of defining a new behavior Bark for the Dog base class, then rewriting the Bark method one by one in the 100 concrete classes... Although the process is somewhat troublesome, we still solved the problem. Now all Dogs can Bark.

One day, a new Dog comes. It's called ToyDog, a toy dog that cannot Run or Bark, it's just a toy. So we let ToyDog inherit from the Dog base class and override the Run method and Bark method (the method body is empty because Toy cannot Run or Bark). The problem seems to be perfectly solved, at least there doesn't seem to be any problem for now.

Many days later, the ToyDog factory has a technological innovation. New products are equipped with batteries and can Bark. It doesn't matter, we modified the ToyDog class and implemented Bark. We initialized a ToyDog, it happily BarkBarkBark, after a while, the battery ran out, ToyDog cannot Bark anymore, but our ToyDog class shows it can still Bark...

Now, problems have arisen, right? We find that the most difficult parts to handle in the Dog problem are the Run and Bark behaviors. Once they change, we need to modify concrete Dog classes, or even the Dog base class. This not only takes a lot of time, but all concrete Dog classes implement Run and Bark methods, appearing very bloated. There's also the most important problem: we cannot dynamically modify Dog's behavior. For example, a small Dog runs slowly and barks quietly, but runs quickly and barks loudly when grown up. It also cannot handle the behavior change problem mentioned above caused by battery depletion... This series of problems wants to show us one point: this was a bad design from the beginning.

III. How to Apply the Strategy Pattern?

The Strategy Pattern requires encapsulating the changing parts. First, we need to find the parts that change frequently in the code. In the previous example, what are the changing parts?

1. Run behavior; 2. Bark behavior; 3. Other possible behaviors

Now let's encapsulate these behaviors (taking Run as an example):

package StrategyPattern;

/**

  • @author ayqy
  • Define Run interface

*/ public interface IRun { public void run();//Define run behavior }

package StrategyPattern;

/**
 * @author ayqy
 * Implement RunQuickly behavior
 *
 */
public class RunQuickly implements IRun{

	 @Override
	public void run() {
		System.out.println("it's running quicky.");
	}

}
package StrategyPattern;

/**
 * @author ayqy
 * Implement RunSlowly behavior
 *
 */
public class RunSlowly implements IRun{

	 @Override
	public void run() {
		System.out.println("it's running slowly.");
	}

}
package StrategyPattern;

/**
 * @author ayqy
 * Implement RunNoWay behavior
 *
 */
public class RunNoWay implements IRun{

	 @Override
	public void run() {
		System.out.println("it isn't able to run.");
	}

}

The encapsulation of Bark behavior is similar. After encapsulating the “changes”, our Dog base class also needs to make corresponding changes:

package StrategyPattern;

/**

  • @author ayqy
  • Define Dog base class

*/ public abstract class Dog { IBark bark; IRun run;

public abstract void display();//Display Dog's appearance

public IBark getBark() {
	return bark;
}

public void setBark(IBark bark) {
	this.bark = bark;
}

public IRun getRun() {
	return run;
}

public void setRun(IRun run) {
	this.run = run;
}

}

Note that we only encapsulate the parts that are prone to change (Bark and Run), without encapsulating the Display method (Dog's appearance should be relatively fixed, right?). What is the “changing part”? This requires our careful consideration, depending on the specific scenario.

Now let's look at our new concrete Dog class:

package StrategyPattern;

/**

  • @author ayqy
  • Implement RedDog

*/ public class RedDog extends Dog{

public RedDog()
{
	//Red dog is a pet dog
	//Runs slowly, barks quietly
	super.setRun(new RunSlowly());
	super.setBark(new BarkQuietly());
}

 @Override
public void display() {
	System.out.println("this is a red dog.");
}

}

The class is obviously slimmed down, right? And now we can also dynamically modify Dog's behavior at runtime. See the power of the Strategy Pattern.

IV. Effect Example

Write a test class:

package StrategyPattern;

/**

  • @author ayqy
  • Test Strategy Pattern<br/>
  • Strategy Pattern, simply put, requires encapsulating the “changes” to isolate the impact of “changes” on other parts

*/ public class Test {

/**
 * @param args
 */
public static void main(String[] args) {
	System.out.println("------- Create a Red Dog -------");
	Dog redDog = new RedDog();
	showDogInfo(redDog);
	
	System.out.println("\n------- Create a Black Dog -------");
	Dog blackDog = new BlackDog();
	showDogInfo(blackDog);
	
	System.out.println("\n------- Create a Toy Dog -------");
	Dog toyDog = new ToyDog();
	showDogInfo(toyDog);
	
	System.out.println("\n------- Technological Innovation, Now Toy Dog Can Bark Quietly -------");
	toyDog.setBark(new BarkQuietly());
	showDogInfo(toyDog);
	
	//The code above is not optimal, just to illustrate the Strategy Pattern
	//One obvious problem is Dog redDog = new RedDog();
	//We are coding towards concrete objects, not coding towards interfaces as advocated by design patterns
	//We can define a Dog factory to produce Dog objects for looser coupling between modules
}

/**
 * @param dog
 * Display Dog related information
 */
private static void showDogInfo(Dog dog)
{
	dog.display();
	dog.run.run();
	dog.bark.bark();
}

}

Running result example:

V. Summary

The core of the Strategy Pattern is to encapsulate frequently changing parts. Its function is to isolate the impact of changing parts, avoiding local changes from affecting other fixed parts. It may take more time during design, but it facilitates maintenance, reuse, and extension. In this example, Run and Bark behaviors can be reused in new classes (such as Pig). Once behaviors change, we only need to modify each behavior interface, and at most make simple modifications to the Dog base class to cope with changes calmly.

VI. Correction

The content above has some deviations, but it's barely acceptable. It's just not quite accurate. Below is an accurate description:

The Strategy Pattern's idea is indeed encapsulation, but the “strategy” here does not refer to “encapsulating the changing parts”. This was discovered after repeatedly reading the original text several times (“Head First Design Patterns” Chapter 1 feels a bit not so good, the first pass understanding was off)

The “strategy” here is approximately equivalent to an algorithm

The Strategy Pattern is not easy to understand (although the author tries hard to avoid copying the original text, but万一 understanding it wrongly again would mislead others...), the accurate definition in the book is like this:

The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. This pattern lets the algorithm vary independently from the clients that use it.

After repeatedly reading it several times, the author thinks the original text wants to express this:

  • 1. The core of the Strategy Pattern is the encapsulation of algorithms (there is another design pattern called “Template Method Pattern”, which is also the encapsulation of algorithms. The distinction and connection will be introduced inside, click here to jump>>)
  • 2. Focus on implementing algorithm (strategy) selection, supporting dynamic strategy changes at runtime
  • 3. The concrete implementation is to find out the changing parts, define them as interfaces, each interface corresponds to a set of algorithms, each one is a strategy
  • 4. Algorithms under the same interface can be mutually replaced
  • 5. Algorithms are independent of client code, which is the concrete embodiment of algorithm encapsulation

Comments

No comments yet. Be the first to share your thoughts.

Leave a comment