Typically, the Strategy Pattern lends itself well to situations where we have a number of processes or algorithms (or strategies) that we can select from at runtime based on some criteria.
As with almost all software engineering problems, there are a number of ways in which we can implement this pattern with the tools available. I’ll describe a method that uses Symfony’s DependencyInjection component here.
The Strategy Interface
We start by defining an interface for our strategy classes to implement. The interface will have two methods:
canProcess, which will return
trueif the strategy is able to process the provided data, or
process, which will process the provided data.
The Context Class
The context class is responsible for choosing a suitable strategy from the available ones. In my example, the individual strategies decide if they are suitable. I like this approach because it keeps the context class isolated from any complex decision logic; though if your suitability criteria is simple enough, that logic could be made to live in the context class instead.
The context class is quite simple, we have private field
strategies, containing the instances of our strategies that we will use at runtime; then two methods:
addStrategy- a simple adder for providing new strategies to our context.
handle- our entry point for the strategy pattern which finds a suitable strategy and uses it to process then return the provided data.
We can define as many strategies as we like.
And another one…
If you’re familiar with Symfony’s DependencyInjection component already, this section might be old news to you, so feel free to skip on ahead.
Symfony’s DependencyInjection component provides some great tooling to do this with minimal effort and in a flexible, configurable way.
The DependencyInjection component works by defining services within configuration files. These services are just objects - instances of classes - that each have a name assigned to them which can be used to request the service from within your application.
When a service is first requested, the arguments for that service are provided to the constructor to obtain the service instance. Arguments can be literal values, variable parameters, references to other services or even expressions now. In addition, you can specify calls - method calls to make on the service instance after it is constructed. Services can also be tagged to group related services together.
When the application initially starts up (I.e. with cold caches), the configuration files are parsed into definitions the container is compiled by a number of built-in passes - one resolves the arguments for each service, another detects any circular dependencies, and at the end a PHP file containing the complete Container class is dumped into the application cache. This container class is then used on each page load of the application rather than recompiling the entire configuration into a new container on each page load.
I mentioned above how the container compilation process is made up of built-in passes. Well, we’re actually able to write our own passes that provide us with an opportunity to manipulate the container before it is dumped too.
We’ll use this capability, combined with the tagging and automatic method calling functionality of the Symfony DependencyInjection component to automatically register each of our strategy instances with the context instance at initialisation time.
First we need to register our context class as a service:
Then each of our strategies:
Note that we tagged the strategies with the
strategy service tag.
Adding a Compiler Pass
A Compiler Pass is simply a class that implements the
CompilerPassInterface interface from the
Symfony\Component\DependencyInjection\Compiler namespace. The interface requires just one method implementation:
process, which accepts a
ContainerBuilderinstance. This method is run when the Compiler Pass is executed during the compilation of the container.
Our compiler pass needs to register each of our strategies with the context service:
If we’re using bundles in the full-stack Symfony framework, we can register our Compiler Pass with the container in our Bundle class by adding a method call to our
Now when our bundle is registered with the kernel, our compiler pass will be added to the compile process for the container automatically.
We can test our implementation from any controller, typically by injecting the
context service into the controller’s service as an argument; or if we’re just doing a quick test, we can retrieve it from the container by calling
Adding another strategy is really easy too - just write the class, implementing the
StrategyInterface interface and create a service for it, tagged with the
strategy tag and next time the container is compiled, it’ll be made available to the context class automatically each time the
context service is retrieved from the container.
If you’d like to have a play with the code, I’ve set up a GitHub repository containing a Symfony Standard Edition installation with the exact code seen here running.
The pattern can be adapted and modified depending on the specific requirements that we’re working with.
For example, the logic of the context class could be altered to select multiple acceptable strategies and try all of them, or try a number of acceptable strategies until a successful outcome is obtained. A variation of this pattern is often applied in situations where we need to have multiple parties vote on the state of an entity or value - for instance in Symfony’s Security component, Voters can be used to vote on a user’s ability to access a resource.