5 Software Design Patterns Every Developer Should Know

Software design patterns are best practices adapted by experienced software developers to implement reusable solutions to common problems.

By Tim Trott | Software Engineering | August 7, 2017
1,564 words, estimated reading time 6 minutes.

Developing software is hard, there are many aspects to 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 try to keep their code clean and testable in an attempt to eliminate bugs and too much complexity creeping into their solutions.

Software Design Patterns patterns are powerful tools for developers. They do not describe specifications for software. It's important to understand the concepts that design patterns describe, rather than memorising their exact classes, methods and properties.

Learning to apply patterns appropriately is what developers should strive for. 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 and provides a global access point to it.

There are several instances in software development where one will need to ensure that there is only one instance of a class. One such example, which may be typical for software developers is to ensure there is a single point of access 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 to the single created instance and public static access to the single created instance.

C#
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;
        }
    }
}

To use the item property on the singleton, the instance is referenced. This will ensure that the List collection is always initialised.

C#
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, which is 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.

C#
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 enables 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.

C#
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}";
    }
}

The builder class in its simplest guise 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. By convention, this method is typically named Build or Create or something similar.

We can now make use of our Builder to create a person as follows.

C#
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 probably 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 is used for creating an object but lets the subclass decide which class to instantiate. Rather than defining each object manually, developers can do it programmatically.

The are several circumstances when developing an application when making use of the Factory Method is suitable. This situation includes when a class can't anticipate which class objects it must create when a class uses its subclasses to specify which objects it creates or when you need to localize 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.

C#
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.

C#
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();
        }
    }
}

And 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.

C#
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 different objects on request. Typically you may use the Abstract Factory Pattern, in conjunction 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 which is responsible for creating a factory of related objects without explicitly specifying the derived classes.

This code is intended to illustrate how an Abstract Factory works in concept, the code is intentionally incomplete for brevity.

C#
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:

C#
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);
}
Was this article helpful to you?
 

Related ArticlesThese articles may also be of interest to you

CommentsShare your thoughts in the comments below

If you enjoyed reading this article, or it helped you in some way, all I ask in return is you leave a comment below or share this page with your friends. Thank you.

There are no comments yet. Why not get the discussion started?

We respect your privacy, and will not make your email public. Learn how your comment data is processed.