design pattern

Behavioral Design Patterns: Strategy

Next up in our in-depth Guide to Software Design Patterns series we’ll dig into the strategy design pattern. The strategy pattern is ideal when code should programmatically determine which algorithm, function, or method should be executed at runtime.

In this article we’ll look into a real world example of the strategy design pattern, along with a fully-functional C# code sample illustrating how that pattern can be implemented, so let’s get to it!

In the Real World

The strategy pattern, which is sometimes called a policy pattern, consists of three basic components:

  • Strategy – A interfaced implementation of the core algorithm.
  • Concrete Strategy – An actual implementation of the core algorithm, to be passed to the Client.
  • Client – Stores a local Strategy instance, which is used to perform the core algorithm of that strategy.

The goal of the strategy design pattern is to allow the Client to perform the core algorithm, based on the locally-selected Strategy. In so doing, this allows different objects or data to use different strategies, independently of one another.

To better explain, let’s consider the real world example of a postal service packaging and mailing out various objects. The characteristics of a given object will determine what packing materials are necessary to safely ship it. That is to say, the packaging required to send a letter will differ dramatically from the packaging necessary to send a watermelon or a saxophone.

These different forms of packaging up objects can be thought of as unique packaging strategies. The strategy for most paper mail is typically envelopes and stamps, while the strategy for packaging perishable food may require a box, packing foam, and possibly even dry ice. The power of the strategy design pattern in this scenario is that the post office can look at each individual object, and implement the most suitable strategy to package it safely and efficiently.

Full Code Sample

Below is the full code sample we’ll be using in this article. It can be copied and pasted if you’d like to play with the code yourself and see how everything works.

How It Works In Code

The code sample continues with the packaging analogy and illustrates how code might be used to differentiate between various packaging strategies, which are used based on the type of object that is being shipped.

We begin with the basic Package class, which is not a key component to the strategy pattern, but merely allows us to represent individual packages as they work their way through the system. A Package is our primary form of data.

We also use the PackingMaterials to store some common materials that we’ll use within our packaging strategies. The ultimate goal is to assign a list of PackingMaterials to each unique Package, as determined by the packaging strategy that is implemented.

Speaking of which, now let’s take a look at the IPackagingStrategy interface, and the abstract PackagingStrategy class that implements it:

These are the bread and butter components of the strategy pattern. IPackagingStrategy is the Strategy and defines the core algorithm (the Pack(Package package) method, in this case) that we’ll be using on our Packages. From that interface we create the PackagingStrategy class, which is effectively a Concrete Strategy object. It’s abstract (with a virtual Pack(Package package) method) in this case just to provide a default form of a concrete strategy, from which all our real strategies can inherit. Regardless, as you can see, the Pack(Package package) simply adds any relevant PackingMaterials to the package.Packaging property list.

Now, let’s look at all the aforementioned strategies that are inheriting PackagingStrategy:

We won’t go through an explanation of each, since the comments and code are fairly explanatory, but you can see that DefaultStrategy doesn’t need to implement or override the Pack(Package package) method, since we want to use the default values. Other classes add the appropriate set of materials based on the strategy they are trying to implement. For example, a fragile package needs a lot of protection, so the FragileStrategy uses a box, foam, bubble wrap, and tape to properly secure the package.

The last component is our Client class, which we’ve named Packager to better fit the example:

Within the strategy design pattern, the Client is what connects the data to the strategy. Typically, this is done by assigning a specific, singular Concrete Strategy instance to a local variable within the Client instance. Thus, our code does this within the constructor itself. As we’ll see later, we can also “reuse” an existing Packager by passing a new IPackagingStrategy instance to the Pack(Package package, IPackagingStrategy strategy) method signature, which assigns the passed strategy to the local Strategy property, then proceeds as normal. In effect, the Packager (Client) invokes the core algorithm (Pack(Package package)) of the local Strategy, using the passed data (Package) the main argument.

Alright, let’s put this all together and see how we might actually use the strategy pattern here to send some packages. We have four different objects we’d like to send, so we’ll split each into a singular example:

We start by creating a new Package, the contents of which will be a teddy bear. Nothing too abnormal going on here, so we create a new Packager instance and pass in the DefaultStrategy that we want to use. Once the Packager instance is created, we can call the core algorithm (Pack(Package package) method) and pass in the data (Package instance). The result should be our teddy bear being packaged up using the default strategy, which is confirmed by the log output:

Now, let’s try something a bit more awkward and fragile than a teddy bear — a computer monitor:

Since we need to ensure the security of this package during shipment, we’ll use the FragileStrategy for this package, but everything else is the same as before. Our output shows that the strategy worked, making sure both bubble wrap and foam were used:

While many implementations of the strategy pattern like to make new instances of Clients every time a new Strategy is implemented, our third example shows how easy it is to create a pattern that reuses existing Clients:

To ship a few salmon filets we have reused the existing Packager instance named packager, but implemented a specific strategy by passing PerishableStrategy as the second Pack(...) argument. Since this item is perishable and must be kept cold, our packaging includes dry ice:

Lastly, a normal teddy bear is fine and dandy, but what about a MASSIVE teddy bear? We’d need an OversizedStrategy for particularly large objects, so that’s what we use:

There we have it! I hope this article gave you a bit more information on what the strategy design pattern is, and how it can be easily implemented in your own code. For more information on all the other popular design patterns, head on over to our ongoing design pattern series here!