Introduction

Software design patterns provide us, we can say, a way to organize our codebase. They represent, not an actual solution, but rather a solution template for the problems in modern software development. Great thing is that these templates are not strictly limited to the specific problem, and they can be reused whenever we see fit. This leads to the possibility of using them too often and when they are not required, therefore picking up one of them should be handled with care. This is usually done by a senior developer of the team or by an architect.

The ideas behind them bring us closer to the abstract realm of software development, and by creating software based on these principles we make it more maintainable, in some cases more readable (too much of abstraction can make our code less readable), easier to extend and reuse.

We can split them into three groups:

  • Creational
  • Structural
  • Behavioral

Each of these groups contains a list of patterns (overall there are over 20 of them and I will not list them here) and the pattern we are going to talk about in this article is called Abstract Factory Pattern. It is one of the creational patterns, and it's very commonly used. And to point out, our examples are going to be written in C#.

Abstract Factory Pattern in C#

All software design patterns are here to solve a specific architectural problem, and this one is no exception. Abstract Factory Pattern solves a problem of creating a family of related products without specifying the actual implementations. It represents an interface for creating many related products but the actual implementation is in the specific product factory. So, for each product, we need a specific factory implementing the abstract factory, but considering that the products are from the same family (they implement the same abstract product) we are able to create a specific product inside.

The participants

  • Abstract Factory (can be an abstract class or an interface)
  • Abstract Product (can be an abstract class or an interface)
  • Factory (a concrete factory)
  • Product (a concrete product)
  • Client (a class using the abstract factory and abstract product)

Real World Example

I think that this pattern would be suitable for solving the pizza place problem and give us a way to create a specific pizza when requested. If you think about it, we have a variety of pizzas so we have a specific product (capricciosa, margherita...). On the other hand, everything is a pizza, so we have an abstract product. Every time when someone orders any of them, we still need to make the pizza (we can't make a pancake instead :)) so we have our abstract factory, and that pizza is one of the pizzas listed on our menu, so we need to make the specific pizza, therefore we have our concrete factory. Also, we will include pizza topping as another abstract product in our example. After all, we need to make our pizza as delicious as possible :)

And we are all set, we can start implementing our design! Let's begin by defining our abstract products and abstract factory.

Abstract products:

// pizza 
public interface IPizza { }

// topping
public interface ITopping { }

Abstract factory:

public interface IPizzaFactory {
   public IPizza GetPizza();
   public ITopping GetTopping();
}

After doing this, we are ready to start implementing the concrete objects that will make our pizza store come to life. Let's ensure that our store is able to bake the capricciosa!

Concrete products:

// pizza
public class Capricciosa : IPizza { }

// topping
public class Ketchup: ITopping { }

Concrete factory:

public class CapricciosaFactory : IPizzaFactory 
{ 
  public IPizza GetPizza()
  {
    return new Capricciosa();
  }

  public ITopping GetTopping()
  {
    return new Ketchup();
  }
}

Have you noticed that we are creating our concrete products by hand (using the new keyword)? If you think about it, another pattern could be used here. Do you know which one? I encourage you to write your suggestion in the comments section.

The last thing we need is the client class. This class uses the abstract factory and abstract products to create a specific product based on the given factory. This way we are able to reuse the pizza client to create any type of pizza. Let's back up this with actual implementation.

public class PizzaClient
{
  public PizzaClient(IPizzaFactory factory)
  {
    Pizza = factory.GetPizza();
    Toping = factory.GetTopping();
  }

  public string Bake()
  {
    return $"Pizza " +
      $"{Pizza.GetType().Name} is done. " +
      $"It uses the {Toping.GetType().Name} topping!";
  }

  public IPizza Pizza { get; private set; }
  public ITopping Toping { get; private set; }
}

And we are done! At this point, we can use our client class wherever we need it. Sample code for our pizza baking system would look like this:

var client = new PizzaClient(new CapricciosaFactory());
var result = client.Bake();

// the result value will be: Pizza Capricciosa is done. It uses the Ketchup topping!

Of course, we can introduce dependency injection and let it handle our object instantiation, but this is not the topic of this article. The goal here was to bring close the abstract factory pattern and hopefully help the reader understand it.

What do you think we need to make our pizza store able to bake the Margherita as well? Think about it before reading the hidden section where you can find some guidelines.

Margherita Guidelines
  • First, we need a concrete product class, which is Margherita. This product must implement our abstract product IPizza.

  • Next, let's say that we don't want Ketchup as a topping for it and instead, we will put Mayonnaise. So we need another concrete product, but this one must implement our ITopping abstract product.

  • Last but not least, we need our MargheritaFactory class to implement the abstract factory IPizzaFactory. The GetPizza method should return a new Margherita object and GetTopping method should return a new Mayonnaise object.

In the end, you can use it like this:

var client = new PizzaClient(new MargheritaFactory());
var result = client.Bake();

// the result value will be (if you implement the Bake method the same way): 
// Pizza Margherita is done. It uses the Mayonnaise topping!

I encourage you to give it a shot! Happy coding!

Conclusion

Back to the C#, I must say. When it comes to devinduct coding articles I've written, they have been mostly related to JavaScript, and frameworks around it, but this time it was pure C# baby! And it felt good! I really love .NET and what Microsoft is doing in the software industry.

Software design patterns can help you scale your application and make it easier to introduce new functionalities. But, beware and think hard before you jump into using one of them because when used improperly, they will bring you more harm than good.

If you liked this article, please consider supporting me by buying me a coffee. To stay tuned, follow me on twitter or subscribe here on devinduct.

Thank you for reading and see you in the next article!