Gang of Four (GOF) Design Patterns - Part 1
Introduction and Exploring Creational Patterns
The Gang of Four refers to authors of "Design Patterns: Elements of Reusable Object-Oriented Software" that was published in 1994. The authors are Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Their book describes 23 classic design patterns in object orientated software development. The intention is to encourage best practices, code re-usability and separation of concerns.
The patterns are categorised into three groups:
Creational Patterns
Dealing with object creation mechanisms. These patterns assist us with creating objects in an organised way that is easy and flexible so that we can change them later on if needed.
Structural Patterns
Concerned with how classes and objects are composed to form larger structures. They use inheritance to compose interfaces and define ways to compose objects to obtain new functionalities.
Behavioural Patterns
Concerned with algorithms and the assignment of responsibilities between objects. They describe not just patterns of objects or classes but also the patterns of communication between them. They are like instructions for how different pieces should talk to each other and work together.
In this three part series we will dive into each category and identify real life examples where these patterns are really useful and assist in building quality software.
Creational Design Patterns
Imagine that you own a coffee shop and of course you want to serve a variety of beverages. Each beverage isn't random and has a specific recipe to make it. You have a magnificent machine that is going to make the beverage for you and all your customers want to do is just tell the machine what beverage they would like. This is where Creational Design Patterns can help us. Creational patterns focus on the process of how the object is created and hide most of the complicated details of how we put the pieces together. It also gives us flexibility so that you can keep adding new beverages later on. Lets take a look at what these patterns are:
Factory
A blueprint for creating things. You define an interface for creating objects but leave the detail to the subclasses. This allows your different subclasses to create objects of different types using the same method.
Abstract Factory
Provides a way for creating families of related or dependent objects without specifying their concrete classes. It is a way of organising groups of things that relate to each other.
Singleton
This ensures that there is always just one instance of a class. You can access this instance from anywhere which makes it useful for when you want a single point of control of coordination in your application. Imagine it like being a single chef in your restaurant. You only have one and the chef is a single point of contact for coordination or control.
Builder
Separates the construction of a complex object from its representation so that the same construction process can create different representations. It helps you build something complex step by step without worrying too much about the details.
Prototype
Specifies the kinds of objects to create using a prototypical instance and creates new objects by copying this prototype. Instead of creating something from scratch you can make a copy of an existing one and tweak it a bit for what you need.
The Factory Pattern
We have a beverage maker and it's job is to simply create a beverage. We have various beverage makers such as coffee, tea and soda.
public interface IBeverageMaker
{
string MakeBeverage();
}
public class CoffeeMaker : IBeverageMaker
{
public string MakeBeverage()
{
return "I just made a coffee!";
}
}
public class SodaMaker : IBeverageMaker
{
public string MakeBeverage()
{
return "I just made a soda!";
}
}
public class TeaMaker : IBeverageMaker
{
public string MakeBeverage()
{
return "I just made a tea!";
}
}
Now we need a factory that will return as a beverage maker based on a selection:
public class BeverageMakerFactory
{
public IBeverageMaker CreateBeverageMaker(BeverageType beverageType)
{
return beverageType switch
{
BeverageType.Coffee => new CoffeeMaker(),
BeverageType.Tea => new TeaMaker(),
BeverageType.Soda => new SodaMaker(),
_ => throw new ArgumentOutOfRangeException(nameof(beverageType), beverageType, null)
};
}
}
The above is a simple way to implement a factory creation method with a few types. If we have an extensive list in the future you may want to look at a registration mechanism for various types instead of an overly long switch statement. We'll keep it simple for this example and go with the above.
Finally we need a way to register our beverage maker. We can simply do this by registering our beverage maker in the program.cs based on values configured in our appsettings file:
var beverageMakerFactory = new BeverageMakerFactory();
builder.Services.AddSingleton<IBeverageMaker>(
s => beverageMakerFactory
.CreateBeverageMaker((BeverageType)Convert.ToInt32(builder.Configuration["FactorySettings:BeverageType"])));
Now when you get a beverage from our API it will return a beverage based on the configuration that you have supplied:
[Route("api/[controller]")]
[ApiController]
public class FactoriesController : ControllerBase
{
private readonly IBeverageMaker _beverageMaker;
public FactoriesController(IBeverageMaker beverageMaker)
{
_beverageMaker = beverageMaker;
}
[HttpGet]
[Route("Beverage")]
public IActionResult GetBeverage()
{
return Ok(_beverageMaker.MakeBeverage());
}
}
There you have it. A simple way to implement the factory pattern.
The Singleton Pattern
Singleton is especially useful for shared resources like configuration handlers, loggers, caching, connections etc.
We have a ConfigHandler class following the singleton pattern:
public sealed class ConfigHandler
{
private static ConfigHandler instance = null;
private static readonly object padlock = new object();
private ConfigHandler()
{
}
public static ConfigHandler Instance
{
get
{
lock (padlock)
{
if (instance == null)
{
instance = new ConfigHandler();
}
return instance;
}
}
}
public string GetConfiguration()
{
return "You got the configuration";
}
}
Why the lock? The lock here is to ensure thread safety to ensure that only one thread can access the instance creation at a time. There are other ways to achieve this such as using Eager initialisation where the instance is created at the time of the class creation.
public sealed class ConfigHandler
{
private static ConfigHandler instance = new ConfigHandler();
private ConfigHandler()
{
}
public static ConfigHandler Instance
{
get
{
return instance;
}
}
public string GetConfiguration()
{
return "You got the configuration";
}
}
In my API controller I simply access the instance and call the GetConfiguration as needed:
[Route("api/[controller]")]
[ApiController]
public class SingletonsController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
var configHandler = ConfigHandler.Instance;
return this.Ok(configHandler.GetConfiguration());
}
}
If you wanted to prove that it is the same instance give the following a try:
[Route("api/[controller]")]
[ApiController]
public class SingletonsController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
var configHandler = ConfigHandler.Instance;
var configHandler2 = ConfigHandler.Instance;
return this.Ok(configHandler2 == configHandler);
}
}
That covers a simple implementation of the singleton pattern.
That's all for part 1. In part 2 we'll take a look at Structural patterns. As always the code for the above is available on my Github. Play around with it and see how it can help you in developing software.