Strategy Design Pattern
In object-based programming, design patterns support developers with proven solution approaches and templates. Once the right solution scheme has been found, only individual adjustments need to be made. There are currently 70 design patterns in total that are tailored to certain applications. Strategy design patterns focus on the behavior of software.
What is the strategy pattern?
Strategy patterns are among the behavioral patterns that equip software with different solution methods. These strategies include a range of algorithms which are distinct from the actual program and are autonomous (i.e. exchangeable). A strategy design pattern also includes certain specifications and aids for developers. For instance, strategy patterns can describe how to assemble classes, arrange a group of classes, and create objects. What’s special about strategy design patterns is that a variable program and object behavior can also be realized during software runtime.
How the strategy pattern is presented in UML
Strategy patterns are normally designed with Unified Modelling Language (UML). It visualizes design patterns with a standardized notation and uses special characters and symbols. The UML provides various diagram types for object-based programming. A class diagram with at least three basic components is typically used to represent a strategy design pattern:
- Context
- Strategy
- ConcreteStrategy
In the strategy design pattern, the basic components take on special roles: The behavioral patterns of the Context class are outsourced to different Strategy classes. These separate classes contain the algorithms that are referred to as ConcreteStrategies. A reference allows the Context to access the outsourced calculation variations (ConcreteStrategyA, ConcreteStrategyB etc.) where necessary. In this process, it does not interact directly with the algorithms but with an interface.
The Strategy interface encapsulates the calculation variations and can be implemented by all algorithms simultaneously. For interacting with the Context, the generic interface represents just one way of triggering ConcreteStrategy algorithms. Besides the strategy request, the interactions with the Context also include data exchange. The Strategy interface is also involved in strategy changes that can take place during a program’s runtime.
Encapsulation prevents direct access to algorithms and internal data structures. An external instance (client, Context) can only use calculations and functions via defined interfaces. Here, only those methods and data elements of an object are accessible that are relevant to the external instance.
We’ll now explain how the design pattern is implemented in a practical project using a strategy pattern example.
Explaining the strategy pattern with an example
In our example (we are orienting ourselves around the German strategy pattern study project by Philipp Hauer, in which a navigation app is to be implemented with the help of a strategy design pattern. The app should calculate a route based on normal modes of transport. The user can choose between three options:
- Pedestrian (ConcreteStrategyA)
- Car (ConcreteStrategyB)
- Public transport (ConcreteStrategyC)
The structure and function of the necessary strategy pattern becomes clear when these specifications are shown in a UML diagram:
In our example, the client is the graphical user interface (GUI) of a navigation app with buttons for calculating routes. Once the user makes a selection and taps on a button, a concrete route is calculated. The Context (navigator class) has the task of calculating and presenting a range of control points on the map. The navigator class has a method for switching the active routing strategy. This means it is possible to switch between modes of transport via the client buttons.
For example, if the user triggers a command with the pedestrian button of the client, the service “Calculate the pedestrian route” (ConcreteStrategyA) is requested. The method executeAlgorithm() (in our example, the method: calculateRoute (A, B)) accepts a starting point and destination, and returns a collection of route control points. The Context accepts the client command and decides on the right strategy (setStrategy: Pedestrian) based on previously described policies. It delegates the request to the strategy object and its interface via a call.
The currently selected strategy is stored in the Context (navigator class) using getStrategy(). The results of the ConcreteStrategy calculations are used in further processing and the graphical presentation of the route in the navigation app. If the user opts for a different route by clicking on the “Car” button afterwards, for example, the Context switches to the requested strategy (ConcreteStrategyB) and initiates a new calculation by means of another call. At the end of the process, a modified route description is provided for travel by car.
In our example, the pattern mechanism can be implemented with relatively simple code:
Context:
public class Context {
//prescribed standard value (default behavior): ConcreteStrategyA
private Strategy strategy = new ConcreteStrategyA();
public void execute() {
//delegates the behavior to a Strategy object
strategy.executeAlgorithm();
}
public void setStrategy(Strategy strategy) {
strategy = strategy;
}
public Strategy getStrategy() {
return strategy;
}
}
Strategy, ConcreteStrategyA, ConcreteStrategyB:
interface Strategy {
public void executeAlgorithm();
}
class ConcreteStrategyA implements Strategy {
public void executeAlgorithm() {
System.out.println("Concrete Strategy A");
}
}
class ConcreteStrategyB implements Strategy {
public void executeAlgorithm() {
System.out.println("Concrete Strategy B");
}
}
Client:
public class Client {
public static void main(String[] args) {
//Default-Verhalten
Context context = new Context();
context.execute();
//Verhalten ändern
context.setStrategy(new ConcreteStrategyB());
context.execute();
}
}
What are the advantages and disadvantages of the strategy pattern?
The advantages of a strategy pattern become clear when you consider the perspective of a programmer and system administrator. In general, the breakdown into autonomous modules and classes leads to an improved structure of the program code. Programmers of our example app work with more streamlined code segments in the encapsulated sections. This allows the size of the navigator class to be reduced by outsourcing strategies, and forming subclasses is unnecessary in the Context area.
Since the internal dependencies of segments stay within reasonable limits in the leaner and neatly segregated code, changes have fewer implications. Subsequent, often time-consuming reprogramming is not required as often and may even be eliminated altogether. Clearer code segments can also be maintained better in the long-term, while troubleshooting and diagnostics are simplified.
The controls benefit too since the example app can be equipped with a user-friendly interface. The buttons allow users to easily control the program behavior (route calculation) in a variable manner and conveniently choose between different options.
Since the Context of the navigation app only interacts with an interface due to the encapsulation of the algorithms, it is independent from the concrete implementation of individual algorithms. If the algorithms are changed at a later time or new strategies introduced, the Context code does not need to be changed. So, route calculation could be quickly and easily expanded with additional ConcreteStrategies for plane and ship routes as well as long-distance transport. The new strategies only need to correctly implement the Strategy interface.
Strategy patterns simplify what is generally the difficult task of programming object-based software, thanks to another advantage. They enable the design of reusable software (modules), which are considered to be particularly difficult to develop. Related Context classes could therefore also use the outsourced strategies for route calculation via an interface and would no longer need to implement these themselves.
Despite the many advantages, the strategy pattern also has some disadvantages. Due to its more complex structure, software design may result in redundancies and inefficiencies in internal communication. For instance, the generic Strategy interface, which all algorithms need to implement equally, may be oversized in individual cases.
An example: After the Context has created and initialized certain parameters, it sends them to the generic interface and the method defined there. But the most recently implemented strategy does not necessarily need all communicated Context parameters and therefore does not process them all. In other words, a provided interface is not always optimally used in the strategy pattern and increased communication with excessive data transfers cannot always be avoided.
In the case of implementation, there is also a close internal dependency between the client and strategies. Since the client makes the selection and requests the specific strategy using a trigger command (in our example, to calculate the pedestrian route), it needs to know the ConcreteStrategies. You should therefore only use this design pattern if strategy and behavior changes are important or essential for the use and functioning of a software program.
These disadvantages can be partially circumvented or offset. For example, the number of object instances that can accrue in a large number in the strategy pattern can often be reduced by an implementation in a flyweight pattern. This measure has a positive effect on the efficiency and memory requirement of an application.
Where is the strategy pattern used?
As a fundamental design pattern in software development, the strategy design pattern is not limited to a certain area of application. Instead, the type of problem is highly relevant for the use of the design pattern. Any software that needs to solve pending tasks and problems with variability, behavior options, and changes is a prime candidate for the design pattern.
For instance, programs that offer different storage formats for files or various sort and search functions can use strategy design patterns. Likewise in data compression, programs are used that implement different compression algorithms based on the design pattern. This way, they can variably convert videos into a desired space-saving file format or restore compressed archive files (e.g. ZIP or RAR files) into their original state using special unpacking strategies. Another example is saving a document or an image in different file formats.
What’s more, the design pattern is involved in the development and implementation of gaming software, which has to respond flexibly to changing game situations during runtime. Different characters, special equipment, figure behaviors, or various moves (special movements of a game character) can be stored in the form of ConcreteStrategies.
Another area of application of strategy patterns is tax software. By exchanging ConcreteStrategies, rates can easily be adjusted for professional groups, countries, and regions. Moreover, programs that convert data into different graphic formats (e.g. as line, circle, or bar charts) use strategy patterns.
More specific applications of strategy patterns can be found in the Java standard library (Java API) and in Java GUI toolkits (e.g. AWT, Swing, and SWT), which use a layout manager in the development and generation of graphical user interfaces. This is able to implement different strategies for configuring components in interface development. Other applications of strategy design patterns include database systems, device drivers, and server programs.
Key properties of the strategy pattern at a glance
Within the extensive range of design patterns, the strategy design pattern distinguishes itself by the following characteristics:
- Behavior-based (behavioral patterns and changes are more easily programmable and implementable; changes are even possible during a program’s runtime)
- Efficiency-oriented (outsourcing simplifies and optimizes code and its maintenance)
- Future-oriented (changes and optimizations are also easy to realize in the medium and long term)
- Aims at extensibility (this is promoted by the modular system and the independence of objects and classes)
- Aims at reusability (e.g. multiple use of strategies)
- Aims at optimized controls, usability, and configurability of software
- Requires extensive conceptual considerations in advance (what can be outsourced to which strategy classes at which points and how?)
Strategy patterns enable efficient and profitable software development in object-based programming with tailored solutions to problems. Even during the design phase, potentially upcoming changes and improvements are optimally prepared. The system is designed for variability and dynamism and can generally be controlled better. Errors and inconsistencies are resolved more quickly. Thanks to reusable and exchangeable components, development costs are saved particularly in complex projects with a long-term time horizon. However, it’s important to find the right balance. Design patterns are often used either too sparingly or too frequently.