Making Strategies — An example of the Strategy pattern with the Factory pattern

Matías Gabriel Katz
7 min readJan 17, 2022

Link al artículo en castellano

Introduction

While I was doing my other article on the Strategy pattern (which you can find here) using DI (Dependency Injection) I realized that I could not miss an article on the same pattern but using another known as Factory.

This is because there may be projects in which you don’t want to use DI, or you use it but we don’t want all the objects to be exposed to a client that consumes our services. And with that, we also achieve that the instances exist on demand only.

The Factory pattern has different ways of implementation. Here I am going to use one known as the Abstract Factory.

This pattern is used when we need an instance of an object without the client knowing how we got it, it being up to the factory and its subtypes how those instances are obtained.

In particular, the Abstract Factory adds an abstraction layer that not only allows me to encapsulate the creation of the objects I need, but also gives me the possibility of grouping factories with common behaviors.

This, in combination with the Strategy pattern, gives us a powerful tool for agile development that turns our application into an easily scalable, maintainable and testable encapsulated system.

Just as in my previous article I commented that Strategy belongs to the family of behavior-oriented design patterns, I now add that Factory belongs to the creational design patterns.

And since it is not the objective for the case of this article to go into much more detail relative to the theoretical but rather to provide a simple example of how to implement it using C# with .NET, let’s go to the practical.

What do we need?

What do we need to implement this pattern? Not much really:

  • A contract that defines what each of the strategies must do.
  • An abstract class that allows us to define the base behavior of our factories.
  • A service that uses the base factory to get what we’re looking for.

Example case

To illustrate this, we will use the same example as in the previous article, but with an extra feature: payment channels (digital or in person). This example is something that happens often in countries like Argentina.

In Argentina, as in many countries, purchases can be made through digital channels (home banking, virtual wallets, etc.) or in person by paying in cash or using credit or debit cards. However, due to our economic context and when payments are made with cards, the possibility of paying using a payment plan is also added.

This generates that the final price may vary depending on the form of payment and the number of payments chosen to carry it out.

From this reality, I created a small example solution as a proof of concept where I use the Strategy pattern in combination with the Factory pattern. The full code for it can be found here. I left all the comments I could in both Spanish and English to make the code easier to read.

Explanation of the code

The solution is built on .NET 5 using C# as the language. It is a console application which makes it light for execution and testing. It is made up of three projects:

  • PoCStrategyFactory: it is the main project and it runs the console application. Here only the test cases are defined and the business layer is called, which will be in charge of calculating the final price of a purchase.
  • PoCStrategyFactory.Business: is the project that represents our business layer. Here is the business logic implemented with both patterns.
  • PoCStrategyFactory.Data: is the project that represents our data layer. It just has model definitions and a static class that has the fixed data and a couple of methods that simulate “database” queries.

In the business layer, to implement the Strategy pattern with Factory, two interfaces and an abstract class were defined:

  • IPaymentService: this is the contract that will allow us to define the service that will call the factory.
  • IPriceStrategy: this is the contract that will allow us to define each of the strategies.
  • PaymentFactory: is the abstract class that defines the base behavior of specific factories and will allow them to resolve strategies.

IPaymentService Interface

To implement IPaymentService a class called PaymentService was created.

In it there is a method called CalculatePrice that receives the information of the payment to be made, which in turn contains the type of payment channel.

With this data from the payment channel, the static method CreatePriceFactory of the PaymentFactory class is called, which we will explain in a moment. For now, what we know is that this method will return an instance of an object that inherits from PaymentFactory.

With that instance we will call the CalculatePrice method of the abstract factory, passing it the information of the payment that we want to make. The result of this method will be returned as the answer to the final price.

PaymentFactory abstract class

So let’s move on to the explanation of the PaymentFactory abstract class. This class has three methods:

  • GetPriceStrategy: it is a protected and abstract method intended for each specific factory to determine the strategies it needs to use to resolve what is requested. For our example, the only thing the method needs as an input parameter is the payment method type. The method is protected because we want it to only be visible to the class and whoever inherits from it.
  • CalculatePrice: it is a public and virtual method (which means that it can be overridden if necessary) that receives the payment information as an input parameter. From its payment method type, the GetPriceStrategy abstract method will be called to obtain a strategy. With this we will call the GetFinalPrice method passing it the information of the payment that we want to make and we will return the value of the final price.
  • CreatePriceFactory: it is a public and static method that, based on the type of payment channel, allows us to obtain the appropriate factory to calculate the final price of a purchase.

In our example, two factories were created that inherit from PaymentFactory and that will be responsible for implementing the GetPriceStrategy method where the available strategies will be defined and how to instantiate them:

  • DigitalPaymentFactory
  • InPersonPaymentFactory

IPriceStrategy interface

Finally, to complete the implementation, 4 strategies based on IPriceStrategy were created, one for each payment method:

  • CashStrategy: it is the strategy that is responsible for calculating the final price when the payment is in cash.
  • CreditCardStrategy: it is the strategy that is responsible for calculating the final price when payment is by credit card.
  • DebitCardStrategy: it is the strategy that is responsible for calculating the final price when payment is by debit card.
  • DigitalStrategy: it is the strategy that is responsible for calculating the final price when the payment is through a digital channel.

At this point we have everything we need when it comes to implementing the design patterns.

Considering this, before explaining the last part of the example code, let’s list some benefits of this way of working:

  • How the instance of each object is resolved becomes a matter of business logic, exposing only what we need and nothing more.
  • In a simple way, each factory solves what it needs and a division of responsibilities is made.
  • The implementation of new factories or the modification of existing ones is simple and modular, which makes maintenance and scalability very simple and practical.
  • Each strategy takes responsibility for knowing how its problems are solved regardless of how others do.
  • If there are problems to solve, it will be easier to identify where they are just by finding out the strategy that is being implemented.
  • Implementing new strategies is easy because it is simply a matter of creating a new class based on the strategy contract and adding it to the responsible factory.
  • Modifying existing strategies becomes a more optimal process that reduces risks in the rest of the processes because the logic is modularized.
  • The combination of both patterns allows obtaining instances on demand, thus optimizing the use of resources.
  • We avoid “spaghetti” type classes by creating much more readable code. There is nothing more horrible in a code than finding kilometric classes that do everything.
  • It allows to clearly follow the principles of SOLID and KISS.
  • In short, all this implies that the readability, scalability and maintainability of the code becomes more optimal.

Code execution

Now let’s see the last part, which is the registration by injection of service dependencies and the execution of the test cases.

In our console application we have installed a package that will give us everything we need to use dependency injection: Microsoft.Extensions.Hosting.

We then need to define a static private property that will hold the dependency injection engine service provider instance:

private static IServiceProvider _serviceProvider;

We also have to remember to have a method to dispose of the resources once they are no longer used:

Then we add a method that will allow us to define the registry by dependency injection. In this method we will do the following:

  • Create an instance of the ServiceCollection class.
  • Register the implementation of the service.
  • Instantiate and assign the service provider.

Note that I use dependency injection only for the service, everything else is handled by the classes that implement the Factory and Strategy patterns.

At this point you already have everything you need. So in the Main method of the console application, we simply call the service registration method, get the service instance, execute each of the test cases by calling the service method which internally uses PaymentFactory for calculation of the prices and, finally, we release the resources.

Conclusion

Although it seems long, notice that to implement these design patterns, only two interfaces and an abstract class were necessary. With which you can see how simple it is to implement Strategy and Factory together.

When we have several problems whose entry point is the same but the way to solve each one is different, the Strategy design pattern is one of the most optimal.

And if we combine it with the Factory pattern to internally resolve the instance of each strategy, we achieve a level of abstraction that greatly simplifies the code.

It is very easy to implement and apply using C# so I encourage you to build your own examples and solutions. Once you start you’ll see that it’s hard not to want to use them together.

--

--

Matías Gabriel Katz

I am a full-stack developer specialized .NET and SQL Server. I’m a big fan SOLID, DRY, KISS and YAGNI principles.