Category The Strategy design pattern

Method injection – Dependency Injection

ASP.NET Core supports method injection only at a few locations, such as in a controller’s actions (methods), the Startup class (if you are using the pre-.NET 6 hosting model), and the middleware’s Invoke or InvokeAsync methods. We cannot liberally use method injection in our classes without some work on our part.Method injection is also used to inject optional dependencies into classes. We can also validate those at runtime using null checks or any other required logic.

I recommend aiming for constructor injection whenever you can. We should only resort to method injection when it’s our sole option or when it brings added value to our design.

For example, in a controller, injecting a transient service in the only action that needs it instead of using constructor injection could save a lot of useless object instantiation and, by doing so, increase performance (less instantiation and less garbage collection). This can also lower the number of class-level dependencies a single class has.

Manually injecting a dependency in a method as an argument is valid. Here’s an example, starting with the classes:

namespace CompositionRoot.ManualMethodInjection;
public class Subject
{
    public int Operation(Context context)
    {
        // …
       
return context.Number;
    }
}
public class Context
{
    public required int Number { get; init; }
}

The preceding code represents the Subject class that consumes an instance of the Context from its Operation method. It then returns the value of its Number property.

This example follows a similar pattern to injecting an HttpContext into an endpoint delegate. In that case, the HttpContext represents the current HTTP request. In our case, the Context contains only an arbitrary number we use in the consuming code next.

To test that our code does as it should, we can write the following test:

[Fact]
public void Should_return_the_value_of_the_Context_Number_property()
{
    // Arrange
    var subject = new Subject();
    var context = new Context { Number = 44 };
    // Act
    var result = subject.Operation(context);
    // Assert
    Assert.Equal(44, result);
}

When we run the test, it works. We successfully injected the context into the subject. Now to simulate a more complex system, let’s have a look at a theory that does the same more dynamically:

[Theory]
[MemberData(nameof(GetData))]
public void Showcase_manual_method_injection(
    Subject subject, Context context, int expectedNumber)
{
    // Manually injecting the context into the
    // Operation method of the subject.
   
var number = subject.Operation(context);
    // Validate that we got the specified context.
   
Assert.Equal(expectedNumber, number);
}

The preceding code showcases the same concept, but xUnit injects the dependencies into the method, which is closer to what would happen in a real program. Remember, we want to remove the new keywords from our life!

The rest of the implementation is not important. I only pieced the simulation together to showcase this scenario. One interesting detail is that the Subject is always the same (singleton) while the Context is always different (transient), leading to a different outcome every time (Context { Number = 0 }, Context { Number = 1 }, and Context { Number = 2 }).

Having explored how to inject dependencies, we are ready to roll up our sleeves and dive into hands-on coding.

Revisiting the Strategy pattern – Dependency Injection

In this section, we leverage the Strategy pattern to compose complex object trees and use DI to dynamically create those instances without using the new keyword, moving away from being control freaks and toward writing DI-ready code.The Strategy pattern is a behavioral design pattern we can use to compose object trees at runtime, allowing extra flexibility and control over objects’ behavior. Composing our objects using the Strategy pattern makes our classes smaller, easier to test and maintain, and puts us on the SOLID path.From now on, we want to compose objects and lower the amount of inheritance to a minimum. We call that principle composition over inheritance. The goal is to inject dependencies (composition) into the current class instead of depending on base class features (inheritance). Additionally, this approach enables us to pull out behaviors and place them in separate classes, adhering to the Single Responsibility Principle (SRP) and Interface Segregation Principle (ISP). We can reuse these behaviors in one or more different classes through their interface, embodying the Dependency Inversion Principle (DIP). This strategy promotes code reuse and composition.The following list covers the most popular ways of injecting dependencies into objects, allowing us to control their behaviors from the outside by composing our objects:

  • Constructor injection
  • Property injection
  • Method injection

We can also get dependencies directly from the container. This is known as the Service Locator (anti-)pattern. We explore the Service Locator pattern later in this chapter.

Let’s look at some theory and then jump into the code to see DI in action.

Constructor injection

Constructor injection consists of injecting dependencies into the constructor as parameters. This is the most popular and preferred technique by far. Constructor injection is useful for injecting required dependencies; you can add null checks to ensure that, also known as the guard clause (see the Adding a guard clause section).

Property injection

The built-in IoC container does not support property injection out of the box. The concept is to inject optional dependencies into properties. Most of the time, you want to avoid doing this because property injection leads to optional dependencies, leading to nullable properties, more null checks, and often avoidable code complexity. So when we think about it, it is good that ASP.NET Core left this one out of the built-in container.You can usually remove the property injection requirements by reworking your design, leading to a better design. If you cannot avoid using property injection, use a third-party container or find a way to build the dependency tree yourself (maybe leveraging one of the Factory patterns).Nevertheless, from a high-level view, the container would do something like this:

  1. Create a new instance of the class and inject all required dependencies into the constructor.
  2. Find extension points by scanning properties (this could be attributes, contextual bindings, or something else).
  3. For each extension point, inject (set) a dependency, leaving unconfigured properties untouched, hence its definition of an optional dependency.

There are a couple of exceptions to the previous statement regarding the lack of support:

  • Razor components (Blazor) support property injection using the [Inject] attribute.
  • Razor contains the @inject directive, which generates a property to hold a dependency (ASP.NET Core manages to inject it).

We can’t call that property injection per se because they are not optional but required, and the @inject directive is more about generating code than doing DI. They are more about an internal workaround than “real” property injection. That is as close as .NET gets from property injection.

I recommend aiming for constructor injection instead. Not having property injection should not cause you any problems. Often, our need for property injection stems from less-than-optimal design choices, whether from our design strategies or a framework we’re utilizing.

Next, we look at method injection.

Project – Registering the Demo Feature – Dependency Injection

Let’s explore registering the dependencies of the Demo Feature. That feature contains the following code:

namespace CompositionRoot.DemoFeature;
public class MyFeature
{
    private readonly IMyFeatureDependency _myFeatureDependency;
    public MyFeature(IMyFeatureDependency myFeatureDependency)
    {
        _myFeatureDependency = myFeatureDependency;
    }
    public void Operation()
    {
        // use _myFeatureDependency
    }
}
public interface IMyFeatureDependency { }
public class MyFeatureDependency : IMyFeatureDependency { }

As we can see, there is nothing complex but two empty classes and an interface. Remember that we are exploring the registration of dependencies, not what to do with them or what they can do—yet.Now, we want the container to serve an instance of the MyFeatureDependency class when a dependency requests the IMyFeatureDependency interface as the MyFeature class does. We want a singleton lifetime.To achieve this, in the Program.cs file, we can write the following code:

builder.Services.AddSingleton<MyFeature>();
builder.Services.AddSingleton<IMyFeatureDependency, MyFeatureDependency>();

We can also chain the two method calls instead:

builder.Services
    .AddSingleton<MyFeature>()
    .AddSingleton<IMyFeatureDependency, MyFeatureDependency>()
;

However, this is not yet elegant. What we want to achieve is this:

builder.Services.AddDemoFeature();

To build that registration method, we can write the following extension method:

using CompositionRoot.DemoFeature;
namespace Microsoft.Extensions.DependencyInjection;
public static class DemoFeatureExtensions
{
    public static IServiceCollection AddDemoFeature(this IServiceCollection services)
    {
        return services
            .AddSingleton<MyFeature>()
            .AddSingleton<IMyFeatureDependency, MyFeatureDependency>()
        ;
    }
}

As highlighted, the registration is the same but uses the services parameter, which is the extended type, instead of the builder.Services (builder does not exist in that class, yet the services parameter is the same object as the builder.Services property).If you are unfamiliar with extension methods, they come in handy for extending existing classes, like we just did. Besides having a static method inside a static class, the this keyword next to the first parameter determines whether it is an extension method.For example, we can build sophisticated libraries that are easy to use with a set of extension methods. Think System.Linq for such a system.Now that we learned the basics of dependency injection, there is one last thing to cover before revisiting the Strategy design pattern.

Using external IoC containers

ASP.NET Core provides an extensible built-in IoC container out of the box. It is not the most powerful IoC container because it lacks some advanced features, but it does the job for most applications.Rest assured; we can change it to another one if need be. You might also want to do that if you are used to another IoC container and want to stick to it.Here’s the strategy I recommend:

  1. Use the built-in container, as per Microsoft’s recommendation.
  2. When you can’t achieve something with it, look at your design and see if you can redesign your feature to work with the built-in container and simplify your design.
  3. If it is impossible to achieve your goal, see if extending the default container using an existing library or coding the feature yourself is possible.
  4. If it is still impossible, explore swapping it for another IoC container.

Assuming the container supports it, it is super simple to swap. The third-party container must implement the IServiceProviderFactory<TContainerBuilder> interface. Then, in the Program.cs file, we must register that factory using the UseServiceProviderFactory<TContainerBuilder> method like this:

var builder = WebApplication.CreateBuilder(args);
builder.Host.UseServiceProviderFactory<ContainerBuilder>(new ContainerBuilderFactory());

In this case, the ContainerBuilder and ContainerBuilderFactory classes are just wrappers around ASP.NET Core, but your third-party container of choice should provide you with those types. I suggest you visit their documentation to know more.Once that factory is registered, we can configure the container using the ConfigureContainer<TContainerBuilder> method and register our dependencies as usual, like this:

builder.Host.ConfigureContainer<ContainerBuilder>((context, builder) =>
{
    builder.Services.AddSingleton<Dependency1>();
    builder.Services.AddSingleton<Dependency2>();
    builder.Services.AddSingleton<Dependency3>();
});

That’s the only difference; the rest of the Program.cs file remains the same.As I sense you don’t feel like implementing your own IoC container, multiple third-party integrations already exist. Here is a non-exhaustive list taken from the official documentation:

  • Autofac
  • DryIoc
  • Grace
  • LightInject
  • Lamar
  • Stashbox
  • Simple Injector

On top of replacing the container entirely, some libraries extend the default container and add functionalities to it. We explore this option in Chapter 11, Structural Patterns.Now that we have covered most of the theory, we revisit the Strategy pattern as the primary tool to compose our applications and add flexibility to our systems.

Registering our dependencies – Dependency Injection

In ASP.NET Core, we register our dependencies in the Program.cs file, which represents the composition root. Since the minimal hosting model, the WebApplicationBuilder exposes the Services property we use to add our dependencies to the container. Afterward, .NET creates the container when it builds the WebApplication instance.Next is a minimal Program.cs file depicting this concept:

var builder = WebApplication.CreateBuilder(args);
// Register dependencies
var app = builder.Build();
// The IoC container is now available
app.Run();

Then, we use the builder.Services property to register our dependencies in that IServiceCollection implementation. Here’s an example of registering some dependencies:

builder.Services.AddSingleton<Dependency1>();
builder.Services.AddSingleton<Dependency2>();
builder.Services.AddSingleton<Dependency3>();

The preceding code registers the dependencies using the singleton lifetime, so we get the same instance each time we request one.

Remember to compose the program in the composition root. That removes the need for those pesky new keywords spread around your code base and all the tight coupling that come with them. Moreover, it centralizes the application’s composition into that location, creating the plan to assemble the LEGO® blocks.

As you may be thinking right now, that can lead to a lot of registration statements in a single location, and you are correct; maintaining such a composition root would be a challenge in almost any application. To address this concern, we introduce an elegant way to encapsulate the registration code next, ensuring it remains manageable.

Registering your features elegantly

As we’ve just discovered, while we should register dependencies in the composition root, we can also arrange our registration code in a structured manner. For example, we can break down our application’s composition into several methods or classes and invoke them from our composition root. Another strategy could be to use an auto-discovery system to automate the registration of certain services.

The critical part is to centralize the program composition in one place.

A common pattern in ASP.NET Core is having special methods like Add[Feature name]. These methods register their dependencies, letting us add a group of dependencies with just one method call. This pattern is convenient for breaking down program composition into smaller, easier-to-handle parts, like individual features. This also makes the composition root more readable.

A feature is the correct size as long as it stays cohesive. If your feature becomes too big, does too many things, or starts to share dependencies with other features, it may be time for a redesign before losing control over it. That’s usually a good indicator of undesired coupling.

To implement this pattern, we use extension methods, making it trivial. Here’s a guide:

  1. Create a static class named [subject]Extensions in the Microsoft.Extensions.DependencyInjection namespace.
  2. Create an extension method that returns the IServiceCollection interface, which allows method calls to be chained.

According to Microsoft’s recommendation, we should create the class in the same namespace as the element we extend. In our case, the IServiceCollection interface lives in the Microsoft.Extensions.DependencyInjection namespace.

Of course, this is not mandatory, and we can adapt this process to our needs. For example, we can define the class in another namespace if we want consumers to add a using statement implicitly. We can also return something else when the registration process can continue beyond that first method, like a builder interface.

Builder interfaces are used to configure more complex features, like ASP.NET Core MVC. For example, the AddControllers extension method returns an IMvcBuilder interface that contains a PartManager property. Moreover, some extension methods target the IMvcBuilder interface, allowing further configuration of the feature by requiring its registration first; that is, you can’t configure IMvcBuilder before calling AddControllers. You can also design your features to leverage that pattern when needed.

Let’s explore a demo.

What is dependency injection? – Dependency Injection

Before you begin: Join our book community on Discord

Give your feedback straight to the author himself and chat to other early readers on our Discord server (find the “architecting-aspnet-core-apps-3e” channel under EARLY ACCESS SUBSCRIPTION).

https://packt.link/EarlyAccess

This chapter explores the ASP.NET Core Dependency Injection (DI) system, how to leverage it efficiently, and its limits and capabilities.We learn to compose objects using DI and delve into the Inversion of Control (IoC) principle. As we traverse the landscape of the built-in DI container, we explore its features and potential uses.Beyond practical examples, we lay down the conceptual foundation of Dependency Injection to understand its purpose, its benefits, and the problems it solves and to lay down the ground for the rest of the book as we rely heavily on DI.We then return to the first three Gang of Four (GoF) design patterns we encountered, but this time, through the lens of Dependency Injection. By refactoring these patterns using DI, we gain a more holistic understanding of how this powerful design tool influences the structure and flexibility of our software.Dependency Injection is a cornerstone in your path toward mastering modern application design and its transformative role in developing efficient, adaptable, testable, and maintainable software.In this chapter, we cover the following topics:

  • What is dependency injection?
  • Revisiting the Strategy pattern
  • Understanding guard clauses
  • Revisiting the Singleton pattern
  • Understanding the Service Locator pattern
  • Revisiting the Factory pattern

What is dependency injection?

DI is a way to apply the Inversion of Control (IoC) principle. IoC is a broader version of the dependency inversion principle (the D in SOLID).The idea behind DI is to move the creation of dependencies from the objects themselves to the composition root. That way, we can delegate the management of dependencies to an IoC container, which does the heavy lifting.

An IoC container and a DI container are the same thing—they’re just different words people use. I use both interchangeably in real life, but I stick to IoC container in the book because it seems more accurate than DI container.

IoC is the concept (the principle), while DI is a way of inverting the flow of control (applying IoC). For example, you apply the IoC principle (inverting the flow) by injecting dependencies at runtime (doing DI) using a container. Feel free to use any or both.

Next, we define the composition root.

The composition root

A critical concept behind DI is the composition root. The composition root is where we tell the container about our dependencies and their expected lifetime: where we compose our dependency trees. The composition root should be as close to the program’s starting point as possible, so from ASP.NET Core 6 onward, the composition root is in the Program.cs file. In the previous versions, it was in the Program or Startup classes.Next, we explore how to leverage DI to create highly adaptable systems.

Conclusion – Strategy, Abstract Factory, and Singleton Design Patterns

The Singleton pattern allows the creation of a single instance of a class for the whole lifetime of the program. It leverages a private static field and a private constructor to achieve its goal, exposing the instantiation through a public static method or property. We can use a field initializer, the Create method itself, a static constructor, or any other valid C# options to encapsulate the initialization logic.Now let’s see how the Singleton pattern can help us (not) follow the SOLID principles:

  • S: The singleton violates this principle because it has two clear responsibilities:
    • It has the responsibility for which it has been created (not illustrated here), like any other class.
    • It has the responsibility of creating and managing itself (lifetime management).
  • O: The Singleton pattern also violates this principle. It enforces a single static instance, locked in place by itself, which limits extensibility. It is impossible to extend the class without changing its code.
  • L: There is no inheritance directly involved, which is the only good point.
  • I: No C# interface is involved, which violates this principle. However, we can look at the class interface instead, so building a small targeted singleton instance would satisfy this principle.
  • D: The singleton class has a rock-solid hold on itself. It also suggests using its static property (or method) directly without using an abstraction, breaking the DIP with a sledgehammer.

As you can see, the Singleton pattern violates all the SOLID principles but the LSP and should be used cautiously. Having only a single instance of a class and always using that same instance is a common concept. However, we explore more proper ways to do this in the next chapter, leading me to the following advice: do not use the Singleton pattern, and if you see it used somewhere, try refactoring it out.

I suggest avoiding static members that create global states as a general good practice. They can make your system less flexible and more brittle. There are occasions where static members are worth using, but try keeping their number as low as possible. Ask yourself if you can replace that static member or class with something else before coding one.

Some may argue that the Singleton design pattern is a legitimate way of doing things. However, in ASP.NET Core, I am afraid I have to disagree: we have a powerful mechanism to do it differently, called dependency injection. When using other technologies, maybe, but not with modern .NET.

Summary

In this chapter, we explored our first GoF design patterns. These patterns expose some of the essential basics of software engineering, not necessarily the patterns themselves, but the concepts behind them:

  • The Strategy pattern is a behavioral pattern that we use to compose most of our future classes. It allows swapping behavior at runtime by composing an object with small pieces and coding against interfaces, following the SOLID principles.
  • The Abstract Factory pattern brings the idea of abstracting away object creation, leading to a better separation of concerns. More specifically, it aims to abstract the creation of object families and follow the SOLID principles.
  • Even if we defined it as an anti-pattern, the Singleton pattern brings the application-level objects to the table. It allows the creation of a single instance of an object that lives for the whole lifetime of a program. The pattern violates most SOLID principles.

We also peeked at the Ambient Context code smell, which is used to create an omnipresent entity accessible from everywhere. It is often implemented as a singleton and brings a global state object to the program.The next chapter explores how dependency injection helps us compose complex yet maintainable systems. We also revisit the Strategy, the Factory, and the Singleton patterns to see how to use them in a dependency-injection-oriented context and how powerful they really are.

Fun fact – Strategy, Abstract Factory, and Singleton Design Patterns

Many years ago, before the JavaScript frameworks era, I fixed a bug in a system where some function was overriding the value of undefined due to a subtle error. This is an excellent example of how global variables could impact your whole system and make it more brittle. The same applies to the Ambient Context and Singleton patterns in C#; globals can be dangerous and annoying.

Rest assured that, nowadays, browsers won’t let developers update the value of undefined, but it was possible back then.

Now that we’ve discussed global objects, an ambient context is a global instance, usually available through a static property. The Ambient Context pattern can bring good things, but it is a code smell that smells bad.

There are a few examples in .NET Framework, such as System.Threading.Thread.CurrentPrincipal and System.Threading.Thread.CurrentThread, that are scoped to a thread instead of being purely global like most static members. An ambient context does not have to be a singleton, but that is what they are most of the time. Creating a non-global (scoped) ambient context is harder and requires more work.

Is the Ambient Context pattern good or bad? I’d go with both! It is useful primarily because of its convenience and ease of use. Most of the time, it could and should be designed differently to reduce its drawbacks.There are many ways of implementing an ambient context, but to keep it brief and straightforward, we are focusing only on the singleton version of the ambient context. The following code is a good example:

public class MyAmbientContext
{
    public static MyAmbientContext Current { get; } = new MyAmbientContext();
    private MyAmbientContext() { }
    public void WriteSomething(string something)
    {
        Console.WriteLine($”This is your something: {something}”);
    }
}

That code is an exact copy of the MySimpleSingleton class, with a few subtle changes:

  • Instance is named Current.
  • The WriteSomething method is new but has nothing to do with the Ambient Context pattern itself; it is just to make the class do something.

If we take a look at the test method that follows, we can see that we use the ambient context by calling MyAmbientContext.Current, just like we did with the last singleton implementation:

[Fact]
public void Should_echo_the_inputted_text_to_the_console()
{
    // Arrange (make the console write to a StringBuilder
    // instead of the actual console)
    var expectedText = “This is your something: Hello World!”
+ Environment.NewLine;
    var sb = new StringBuilder();
    using (var writer = new StringWriter(sb))
    {
        Console.SetOut(writer);
        // Act
        MyAmbientContext.Current.WriteSomething(“Hello World!”);
    }
    // Assert
    var actualText = sb.ToString();
    Assert.Equal(expectedText, actualText);
}

The property could include a public setter or support more complex logic. Building the right classes and exposing the right behaviors is up to you and your specifications.To conclude this interlude, avoid ambient contexts and use instantiable classes instead. We see how to replace a singleton with a single instance of a class using dependency injection in the next chapter. That gives us a more flexible alternative to the Singleton pattern. We can also create a single instance per HTTP request, which saves us the trouble of coding it while eliminating the disadvantages.

An alternate (better) way – Strategy, Abstract Factory, and Singleton Design Patterns

Previously, we used the “long way” of implementing the Singleton pattern and had to implement a thread-safe mechanism. Now that classic is behind us. We can shorten that code and even get rid of the Create() method like this:

public class MySimpleSingleton
{
    public static MySimpleSingleton Instance { get; } = new MySimpleSingleton();
    private MySimpleSingleton() { }
}

The preceding code relies on the static initializer to ensure that only one instance of the MySimpleSingleton class is created and assigned to the Instance property.

This simple technique should do the trick unless the singleton’s constructor executes some heavy processing.

With the property instead of a method, we can use the singleton class like this:

MySimpleSingleton.Instance.SomeOperation();

We can prove the correctness of that claim by executing the following test method:

[Fact]
public void Create_should_always_return_the_same_instance()
{
    var first = MySimpleSingleton.Instance;
    var second = MySimpleSingleton.Instance;
    Assert.Same(first, second);
}

It is usually best to delegate responsibilities to the language or the framework whenever possible like we did here with the property initializer. Using a static constructor would also be a valid, thread-safe alternative, once again delegating the job to language features.

Beware of the arrow operator.

It may be tempting to use the arrow operator => to initialize the Instance property like this: public static MySimpleSingleton Instance => new MySimpleSingleton();, but doing so would return a new instance every time. This would defeat the purpose of what we want to achieve. On the other hand, the property initializer runs only once.

The arrow operator makes the Instance property an expression-bodied member, equivalent to creating the following getter: get { return new MySimpleSingleton(); }. You can consult Appendix A for more information about expression-bodies statements.

Before we conclude the chapter, the Singleton (anti-)pattern also leads to a code smell.

Code smell – Ambient Context

That last implementation of the Singleton pattern led us to the Ambient Context pattern. We could even call the Ambient Context an anti-pattern, but let’s just state that it is a consequential code smell.I do not recommend using ambient contexts for multiple reasons. First, I do my best to avoid anything global; an ambient context is a global state. Globals, like static members in C#, can look very convenient because they are easy to access and use. They are always there and accessible whenever needed: easy. However, they bring many drawbacks in terms of flexibility and testability.When using an ambient context, the following occurs:

  • Tight coupling: global states lead to less flexible systems; consumers are tightly coupled with the ambient context.
  • Testing difficulty: global objects are harder to replace, and we cannot easily swap them for other objects, like a mock.
  • Unforseen impacts: if some part of your system messes up your global state, that may have unexpected consequences on other parts of your system, and you may have difficulty finding out the root cause of those errors.
  • Potential misuse: developers could be tempted to add non-global concerns to the ambient context, leading to a bloated component.

The Singleton design pattern – Strategy, Abstract Factory, and Singleton Design Patterns

The Singleton design pattern allows creating and reusing a single instance of a class. We could use a static class to achieve almost the same goal, but not everything is doable using static classes. For example, a static class can’t implement an interface. We can’t pass an instance of a static class as an argument because there is no instance. We can only use static classes directly, which leads to tight coupling every time.The Singleton pattern in C# is an anti-pattern, and we should rarely use it, if ever, and use dependency injection instead. That said, it is a classic design pattern worth learning to at least avoid implementing it. We explore a better alternative in the next chapter.Here are a few reasons why we are covering this pattern:

  • It translates into a singleton scope in the next chapter.
  • Without knowing about it, you cannot locate it, try to remove it, or avoid its usage.
  • It is a simple pattern to explore.
  • It leads to other patterns, such as the Ambient Context pattern.

Goal

The Singleton pattern limits the number of instances of a class to one. Then, the idea is to reuse the same instance subsequently. A singleton encapsulates both the object logic itself and its creational logic. For example, the Singleton pattern could lower the cost of instantiating an object with a large memory footprint since the program instantiates it only once.Can you think of a SOLID principle that gets broken right there?The Singleton pattern promotes that one object must have two responsibilities, breaking the Single Responsibility Principle (SRP). A singleton is the object itself and its own factory.

Design

This design pattern is straightforward and is limited to a single class. Let’s start with a class diagram:

 Figure 7.6: Singleton pattern class diagramFigure 7.6: Singleton pattern class diagram 

The Singleton class is composed of the following:

  • A private static field that holds its unique instance.
  • A public static Create() method that creates or returns the unique instance.
  • A private constructor, so external code cannot instantiate it without passing by the Create method.

You can name the Create() method anything or even get rid of it, as we see in the next example. We could name it GetInstance(), or it could be a static property named Instance or bear any other relevant name.

We can translate the preceding diagram to the following code:

public class MySingleton
{
    private static MySingleton?
_instance;
    private MySingleton() { }
    public static MySingleton Create()
    {
        _instance ??= new MySingleton();
        return _instance;
    }
}

The null-coalescing assignment operator ??= assigns the new instance of MySingleton only if the _instance member is null. That line is equivalent to writing the following if statement:

if (_instance == null)
{
    _instance = new MySingleton();
}

Before discussing the code more, let’s explore our new class’s behavior. We can see in the following unit test that MySingleton.Create() always returns the same instance as expected:

public class MySingletonTest
{
    [Fact]
    public void Create_should_always_return_the_same_instance()
    {
        var first = MySingleton.Create();
        var second = MySingleton.Create();
        Assert.Same(first, second);
    }
}

And voilà! We have a working Singleton pattern, which is extremely simple—probably the most simple design pattern that I can think of.Here is what is happening under the hood:

  1. The first time that a consumer calls MySingleton.Create(), it creates the first instance of MySingleton. Since the constructor is private, it can only be created from the inside.
  2. The Create method then persists that first instance to the _instance field for future use.
  3. When a consumer calls MySingleton.Create() a second time, it returns the _instance field, reusing the class’s previous (and only) instance.

Now that we understand the logic, there is a potential issue with that design: it is not thread-safe. If we want our singleton to be thread-safe, we can lock the instance creation like this:

public class MySingletonWithLock
{
    private static readonly object _myLock = new();
    private static MySingletonWithLock?
_instance;
    private MySingletonWithLock() { }
    public static MySingletonWithLock Create()
    {
        lock (_myLock)
        {
            _instance ??= new MySingletonWithLock();
        }
        return _instance;
    }
}

In the preceding code, we ensure two threads are not attempting to access the Create method simultaneously to ensure they are not getting different instances. Next, we improve our thread-safe example by making it shorter.

Conclusion– Strategy, Abstract Factory, and Singleton Design Patterns

The Strategy design pattern is very effective at delegating responsibilities to other objects, allowing you to hand over the responsibility of an algorithm to other objects while keeping its usage trivial. It also allows having a rich interface (context) with behaviors that can change at runtime.As we can see, the Strategy pattern is excellent at helping us follow the SOLID principles:

  • S: It helps extract responsibilities from external classes and use them interchangeably.
  • O: It allows extending classes without updating its code by changing the current strategy at runtime, which is pretty much the actual definition of the OCP.
  • L: It does not rely on inheritance. Moreover, it plays a large role in the composition over inheritance principle, helping us avoid inheritance altogether and the LSP.
  • I: By creating smaller strategies based on lean and focused interfaces, the Strategy pattern is an excellent enabler of the ISP.
  • D: The creation of dependencies is moved from the class using the strategy (the context) to the class’s consumer. That makes the context depend on abstraction instead of implementation, inverting the flow of control.

C# Features

If you noticed C# features you are less familiar with, Appendix A explains many of them briefly.

Next, let’s explore the Abstract Factory pattern.

The Abstract Factory design pattern

The Abstract Factory design pattern is a creational design pattern from the GoF. We use creational patterns to create other objects, and factories are a very popular way of doing that.The Strategy pattern is the backbone of dependency injection, enabling the composition of complex object trees, while factories are used to create some of those complex objects that can’t be assembled automatically by a dependency injection library. More on that in the next chapter.

Goal

The Abstract Factory pattern is used to abstract the creation of a family of objects. It usually implies the creation of multiple object types within that family. A family is a group of related or dependent objects (classes).Let’s think about creating automotive vehicles. There are multiple vehicle types, and there are multiple models and makes for each type. We can use the Abstract Factory pattern to model this sort of scenario.

Note

The Factory Method pattern also focuses on creating a single type of object instead of a family. We only cover Abstract Factory here, but we use other types of factories later in the book.

Design

With Abstract Factory, the consumer asks for an abstract object and gets one. The factory is an abstraction, and the resulting objects are also abstractions, decoupling the creation of an object from its consumers.That allows adding or removing families of objects produced together without impacting the consumers (all actors communicate through abstractions).In our case, the family (the set of objects the factory can produce) is composed of a car and a bike, and each factory (family) must produce both objects.If we think about vehicles, we could have the ability to create low- and high-end models of each vehicle type. Here is a diagram representing how to achieve that using the Abstract Factory pattern:

 Figure 7.4: Abstract Factory class diagramFigure 7.4: Abstract Factory class diagram 

In the diagram, we have the following elements:

  • The IVehicleFactory interface represents the Abstract Factory. It defines two methods: one that creates cars of type ICar and another that creates bikes of type IBike.
  • The HighEndVehicleFactory class is a concrete factory implementing the IVehicleFactory interface. It handles high-end vehicle model creation, and its methods return HighEndCar or HighEndBike instances.
  • The LowEndVehicleFactory is a second concrete factory implementing the IVehicleFactory interface. It handles low-end vehicle model creation, and its methods return LowEndCar or LowEndBike instances.
  • LowEndCar and HighEndCar are two implementations of ICar.
  • LowEndBike and HighEndBike are two implementations of IBike.

Based on that diagram, consumers use the concrete factories through the IVehicleFactory interface and should not be aware of the implementation used underneath. Applying this pattern abstracts away the vehicle creation process.