5 Software Design Patterns Every Developer Should KnowSoftware design patterns are best practices adapted by experienced software developers to implement reusable solutions to common problems.

Developing software is complex; there are many aspects of code that developers need to consider when writing the components and libraries they will use to solve problems and provide solutions. Developers often strive to keep their code clean and testable to eliminate bugs and too much complexity creeping into their solutions. To this end software design patterns provide a standard template for common scenarios.
Software Design Patterns patterns are powerful tools for developers. They do not describe specifications for software. Understanding the concepts that design patterns describe is important, rather than memorising their exact classes, methods and properties.
Developers should strive to learn to apply patterns appropriately. Using the incorrect pattern for a situation or applying a design pattern to a trivial solution can over-complicate your code and lead to maintainability issues.
Singleton Software Design Pattern
The primary objective of the Singleton Pattern is to ensure that there is one and only one instance of a class. It provides a global access point.
There are several instances in software development where one must ensure only one instance of a class. One such example, which may be typical for software developers, is providing a single access point to a database engine. Other common usages for Singleton classes could be Service Proxies, Facades, Logging and caching. Singleton classes are typically used in applications to create utility classes.
Common characteristics of singleton pattern implementations include:
- A single constructor (private and parameter-less).
- Sealed class (cannot be inherited).
- Static variable references the single created instance and public static access to the single created instance.
public sealed class DbContext
{
private static volatile DbContext instance;
private static readonly object padlock = new object();
public List<string> Items { get; set; }
public static DbContext Instance
{
get
{
if (instance != null) return instance;
lock (padlock)
{
if (instance == null)
{
instance = new DbContext();
instance.Items new new List<string>();
}
}
return instance;
}
}
}
The instance is referenced to use the item property on the singleton. This will ensure that the List collection is always initialised.
DbContext.Instance.Items.Add("Hello World!");
Prototype Software Design Pattern
The Prototype pattern is specifically used when creating a duplicate object of an existing object while attempting to conserve resources and focus on performance.
The prototype pattern is a creational design pattern in software development. It is used when the type of objects to create is determined by a prototypical instance cloned to produce new objects. This pattern is used to avoid subclasses of an object creator in the client application, as the factory method pattern does, and to avoid the inherent cost of creating a new object in the standard way (e.g., using the 'new' keyword) when it is prohibitively expensive for a given application.
To implement the pattern, declare an abstract base class that specifies a pure virtual clone() method. Any class that needs a "polymorphic constructor" capability derives itself from the abstract base class and implements the clone() operation.
The client, instead of writing code that invokes the "new" operator on a hard-coded class name, calls the clone() method on the prototype, calls a factory method with a parameter designating the particular concrete derived class desired, or invokes the clone() method through some mechanism provided by another design pattern.
public class Shape : ICloneable
{
public string ID { get; set; }
public string Type { get; set; }
public object Clone()
{
return this.MemberwiseClone();
}
}
class Program
{
static void Main(string[] args)
{
var shape = new Shape
{
ID = "Shape1",
Type = "Shape"
};
var shape2 = shape.Clone() as Shape;
Console.WriteLine($"The Cloned Shape ID is { shape2.ID } { shape2.Type }");
Console.WriteLine("The second Shape has the following skills: ");
}
}
The Builder Software Design Pattern
The Builder Pattern is useful for encapsulating and abstracting the creation of objects. It is distinct from the more common Factory Design Pattern because the Builder Pattern contains methods of customising the creation of an object.
Whenever an object can be configured in multiple ways across multiple dimensions, the Builder Pattern can simplify the creation of objects and clarify the intent.
The builder pattern enables developers to hide details of how an object is created and allows developers to vary the internal representation of an object it builds. Each specific Builder is independent of others and the rest of the application, improving Modularity and simplifying and enabling the addition of other Builders.
public class Person
{
public int Id { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
public DateTime DateOfBirth { get; set; }
public Gender Gender { get; set; }
}
public enum Gender
{
Male,
Female
}
public class Person
{
public int Id { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
public DateTime DateOfBirth { get; set; }
public string Occupation { get; set; }
public Gender Gender { get; set; }
public override string ToString()
{
return $"Person with id: {Id} with date of birth
{DateOfBirth.ToLongDateString()} and name
{string.Concat(Firstname, " ",Lastname)} is a {Occupation}";
}
}
In its simplest guise, the builder class is just a series of name constructor methods with arguments; you'll notice that they always return an instance of the class. The final method on the builder class is the final method, which will return the completed object. This method is typically named Build or Create or something similar by convention.
We can now use our Builder to create a person as follows.
class Program
{
static void Main(string[] args)
{
var person = new PersonBuilder()
.Id(10)
.Firstname("Tim")
.Lastname("Trott")
.Gender(Gender.Male)
.DateOfBirth(DateTime.Now)
.Occupation("")
.Build();
Console.WriteLine(person.ToString());
Console.ReadLine();
}
}
Factory Method Software Design Pattern
The factory concept is the most common design pattern and recurs throughout object-oriented programming.
In the factory pattern, developers create an object without exposing the creation logic. An interface creates an object but lets the subclass decide which class to instantiate. Rather than defining each object manually, developers can do it programmatically.
There are several circumstances when developing an application using the Factory Method is suitable. This situation includes when a class can't anticipate which class objects it must create when it uses its subclasses to specify which objects it creates or when you need to localise the knowledge of which class gets created.
In this example, we'll develop a simple factory method that enables the creation of a vehicle depending on the number of wheels required.
public interface IVehicle
{
}
public class Unicycle : IVehicle
{
}
public class Car : IVehicle
{
}
public class Motorbike : IVehicle
{
}
public class Truck : IVehicle
{
}
We can now create a VechicleFactory class with a build method to create a vehicle depending on the number of wheels supplied.
public static class VehicleFactory
{
public static IVehicle Build(int numberOfWheels)
{
switch (numberOfWheels)
{
case 1:
return new UniCycle();
case 2:
case 3:
return new Motorbike();
case 4:
return new Car();
default :
return new Truck();
}
}
}
In the final example code, we see how to use the factory method to create an instance of a particular type of vehicle-based on the number of wheels entered.
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Enter a number of wheels between 1 and 12 to build a vehicle and press enter");
var wheels = Console.ReadLine();
var vehicle = VehicleFactory.Build(Convert.ToInt32(wheels));
Console.WriteLine($" You built a {vehicle.GetType().Name}");
Console.Read();
}
}
Abstract Factory Software Design Pattern
The Abstract Factory Pattern is used when you want to return several related classes of objects, each of which can return several objects on request. Typically, you may use the Abstract Factory Pattern with other factory patterns like the Factory Method Pattern.
The best way to think of the Abstract factory pattern is that it is a super factory or a *factory of factories*. Typically, it is an interface that creates a factory of related objects without explicitly specifying the derived classes.
This code is intended to illustrate how an abstract factory works in concept. Still, the code is intentionally incomplete for brevity.
private static IVehicle GetVehicle(VehicleRequirements requirements)
{
var factory = new VehicleFactory();
IVehicle vehicle;
if (requirements.HasEngine)
{
return factory.MotorVehicleFactory().Create(requirements);
}
return factory.CycleFactory().Create(requirements);
}
public abstract class AbstractVehicleFactory
{
public abstract IVehicleFactory CycleFactory();
public abstract IVehicleFactory MotorVehicleFactory();
}
public class VehicleFactory : AbstractVehicleFactory
{
public override IVehicleFactory CycleFactory()
{
return new Cyclefactory();
}
public override IVehicleFactory MotorVehicleFactory()
{
return new MotorVehicleFactory();
}
}
public class Cyclefactory : IVehicleFactory
{
public IVehicle Create(VehicleRequirements requirements)
{
switch (requirements.Passengers)
{
case 1:
if(requirements.NumberOfWheels == 1) return new Unicycle();
return new Bicycle();
case 2:
return new Tandem();
case 3:
return new Tricyle();
case 4:
if (requirements.HasCargo) return new GoKart();
return new FamilyBike();
default:
return new Bicycle();
}
}
}
We can now use the factory like this:
static void Main(string[] args)
{
var requirements = new VehicleRequirements();
Console.WriteLine("How many wheels do you have ");
requirements.NumberOfWheels = Console.ReadLine();
Console.WriteLine("Do you have an engine ( Y/n )");
var engine = Console.ReadLine();
switch (engine)
{
case "Y":
requirements.Engine = true;
break;
case "N":
requirements.Engine = false;
break;
default:
requirements.Engine = false;
break;
}
Console.WriteLine("How many passengers will you be carrying ? (1 - 10)");
requirements.Passengers = Console.ReadLine();
Console.WriteLine("Will you be carrying cargo");
var cargo = Console.ReadLine();
switch (cargo)
{
case "Y":
requirements.Engine = true;
break;
case "N":
requirements.Engine = false;
break;
default:
requirements.Engine = false;
break;
}
var vehicle = GetVehicle(requirements);
Console.WriteLine(vehicle.GetType().Name);
}