Tactical Design Patterns in C# - Part 1: Creational

What are Design Patterns

a software design pattern is a general, reusable solution to a commonly occurring problem within a given context What does that mean? Well it means we have a general approach to a recurrent problem. In itself it is not an algorithm because it doesn't solve the issue on its own. The are multiple reasons to take those:

  • They are well-defined and increase the readability of your code as other develops can identify the pattern
  • They are reusable. Not like an algorithm which solves one problem
  • It can increase code maintainability

The following graphic shows where design patterns are. They are an abstract concept. Unlike for example clean code where it is clear that you for example have to name variables / functions properly, design patterns are way more general. pattern

We can distinguish between 4 types of design patterns:

  • creational
  • structural
  • behavoriual
  • concurrency

In this part we will have a look at two very commonly used design pattern: the Factory and the Builder pattern. The latter one will have a little twist.

Factory - Pattern

With the Factory pattern we want create objects without knowing the exact type. That decouples the logic how to create an object.

Simple Implementation

First have a look at the simple UML:

uml

The factory can determine which concrete type we create given the SweetType enumeration. It returns always an ISweet. As we can see those design patterns enforce easily decoupling.

using System;

public interface ISweet
{
    int CaloriesPer100Gramms { get;}
}

public class GummyBear : ISweet
{
    public int CaloriesPer100Gramms => 450;
}

public class CandyBar : ISweet
{
    public int CaloriesPer100Gramms => 500;
}

public enum Flavour
{
    Chocolate,
    SoftSweet
}

public class SweetFactory
{
    public ISweet Create(Flavour flavour)
    {
        return flavour switch 
        {
            Flavour.Chocolate => new CandyBar(),
            Flavour.SoftSweet => new GummyBear(),
            _ => throw new NotSupportedException("We don't have that kind of candy")
        };
    }
}
                    
public class Program
{
    public static void Main()
    {
        var sweet = new SweetFactory().Create(Flavour.Chocolate);
        Console.Write($"Calories per 100g: {sweet.CaloriesPer100Gramms}");
    }
}

This will output:

Calories per 100g: 500

Our SweetFactory is the central point which decides which concrete object we want to create given the context. From a consumer point of view we only handle the interface ISweet. Perfect all the domain knowledge in one single object.

(Fluent) Builder Pattern

With the builder pattern we try to decouple the construction of an complex object from its representation. I want to show you a version with a twist: the fluent builder pattern. The fluent builder returns itself so you can use a fluent notation to construct your object. Personally I use this often when it comes down to creating business objects for tests. We can set the properties via default in that way that our business object is valid.

Sample implementation

public class Person
{
    public string Name { get; set; }
    public Job Job { get; set; }
    
    public Person(string name, Job job)
    {
        Name = name ?? throw new ArgumentNullException("name");
        Job = job ?? throw new ArgumentNullException("job");
    }
}

public class Job
{
    public string Title { get; set; }
}

public class PersonBuilder
{
    private string name = "Test name";
    private Job job = new Job { Title = "Random Title" };
    
    public PersonBuilder WithName(string name)
    {
        this.name = name;
        return this;
    }
    
    public PersonBuilder WithJob(Action<JobBuilder> configuration)
    {
        var jobBuilder = new JobBuilder();
        configuration(jobBuilder);
        job = jobBuilder.Build();
        return this;
    }
    
    public Person Build()
    {
        var person = new Person(name, job);		
        return person;
    }
}

public class JobBuilder
{
    private string title = "Title";
    
    public JobBuilder WithTitle(string title)
    {
        this.title = title;
        return this;
    }
    
    public Job Build()
    {
        return new Job { Title = title };
    }
}

We see that we can decouple the logic of creating a person from the logic of creating a job but still in the fluent notation we are able to create multiple related objects with one call.

public static void Main()
{
    var person = new PersonBuilder().WithName("Mike").WithJob(j => j.WithTitle("New Title")).Build();
    Console.Write($"Person {person.Name} has Job: {person.Job.Title}");
}

Prints:

Person Mike has Job: New Title

If we don't care at all for the specific properties and we want just to have a valid Person we call directly the Build method: var person = new PersonBuilder().Build(); and we have a valid domain object. This brings a big advantage when your domain layer emerges. You don't have to update all your tests because you have a new required constructor argument.

Conclusion

We saw how we can easily decouple the logic for creating objects and there specific type with the Factory pattern and how we can decouple the representation of a complex object with how it is constructed with the Builder Pattern.

Both are very commonly used.

The Humble Object Pattern

The Humble Object Pattern is a design pattern to make especially unit testing easier with the goal of separating behaviors that are easy to handle (domain logic) from behaviors that are hard to handle (like external events or dependencies).

So let's have a look at what it is and how you can utilize it.

Design patterns explained with sketches

This article will explain design patterns, which we use on a daily base, with smaller (over)simplified illustrations.

Repository Pattern - A controversy explained

In this blog post, we'll dive into the ins and outs of the repository pattern and examine both its benefits and its potential drawbacks. We will start from the very basic to some more advanced use cases. So let's dive right into it.

An error has occurred. This application may no longer respond until reloaded. Reload x