The Strategy pattern is used when a choice of multiple different but related algorithms can be made at run time. The implementation of each related algorithm exposes the same interface so as to hide the details of implementation from the calling program and the algorithm.
In a more general form, a "Strategy" is nothing more than a class implementation hidden behind an abstract interface. This allows the implementation to be changed without affecting the caller. The Strategy pattern takes this one step further to allow entire algorithms to be hidden behind the interface and then swap in different instances of the interface at run time to change the algorithm being used.
Typically a Strategy pattern implementation specifies the algorithm at the time the program starts up or is configured. It can also be configured at the time a class representing the functionality is instantiated. The algorithm to use is generally selected by the user based on some input. Once set, the algorithm selected does not change until reconfigured or the user selects another algorithm.
The value of a Strategy pattern is the ability to create a basic infrastructure for the program and then allow the user to alter the behavior of that infrastructure using options. Also, additional strategies for each option can be added without changing the basic infrastructure or affecting any other strategy. The drawback is now the functionality of the program is spread out across many different classes and it can be quite difficult following the flow of execution, especially if a strategy can itself have its own set of strategies.
The only tricky part of making use of the Strategy pattern is figuring out what the interface should be for all possible algorithms for a given situation. The simplest approach to solving this is to determine what must the algorithm provide to the caller to allow the caller to get work done then defining an interface that provides that functionality. Then create the implementation of algorithms that satisfies the required functionality.
For example, a program such as dir
or ls
can list files and folders in a variety of formats. This program has one feature: display information about files and folders. This feature can be broken down into two components:
To fetch the information about files and folders, there are several options:
Each of these can be represented by a strategy for reading information about files/folders. That is, an interface can be defined that returns a list of files/folders given a pattern. Then specific implementations of the interface can deal with matching files and recursing through folders.
The same approach can be taken with displaying the information:
Each of these display options could be a strategy. Define an interface that takes a list of information about files/folders and display all or some of the information in the given order.
The "in sorted order" suggests a third strategy is needed: How to sort the list of files/folders. For example, sort by name, length, type, or date, with an option to reverse the order.
The ls
program works (in general) like this (based on input parameters, including default parameters):
With this approach, the pseudo-code for an object-oriented implementation of the ls
program might look like this:
All the really hard stuff happens inside the implementation of each strategy. And additional implementations of each strategy can be added later on without affecting the above pseudo-code.
C++ | C# | Python | C |
---|---|---|---|
ISortEntries interface | ISortEntries interface | Strategy_SortEntries_ClassFactory interface | <Not Applicable> |
Strategy_SortEntries_ClassFactory class | ISortEntries interface | Strategy_SortEntries_ClassFactory class | SortStrategy_Initialize() |
Strategy_SortEntries_ByName class | Strategy_SortEntries_ByName class | Strategy_SortEntries_ByName class | Sort_Entries() _Compare_Name() |
Strategy_SortEntries_ByAge class | Strategy_SortEntries_ByAge class | Strategy_SortEntries_ByAge class | Sort_Entries() _Compare_Age() |
Strategy_SortEntries_ByHeight class | Strategy_SortEntries_ByHeight class | Strategy_SortEntries_ByHeight class | Sort_Entries() _Compare_Height() |
Strategy_ShowEntries_Class | Strategy_ShowEntries_Class class | Strategy_ShowEntries_Class class | Display_Entries() |
As interesting as the above file lister example would be, it would be just a bit too long as an example (like a 1000 lines long). So in a considerably simpler example with only one strategy, a list of people with Name, Age, and Height are sorted in some order based on a selected strategy. The list of individuals is then shown in the sorted order. The sort has a modification where the order can be reversed from ascending to descending.
Note: It is strongly advised not to use the word "Strategy" when naming the interfaces. That name is too generic. Name the interfaces based on what the interfaces are used for and that includes the methods exposed through the interface. The only reason I'm using "Strategy" in this example is to help group the public classes into the patterns being demonstrated.
C++
C#
Python
C
RUST
(Apologies. Doxygen does not understand Rust syntax and therefore cannot colorize the code.)