Revisiting the Factory pattern – Dependency Injection

A factory creates other objects; it is like a literal real-world factory. We explored in the previous chapter how to leverage the Abstract Factory pattern to create families of objects. A factory can be as simple as an interface with one or more Create[Object] methods or, even more, a simple delegate. We explore a DI-oriented simple factory in this section. We are building on top of the Strategy pattern example.In that example, we coded two classes implementing the ILocationService interface. The composition root used the #define preprocessor directive to tell the compiler what bindings to compile. In this version, we want to choose the implementation at runtime.

Not compiling the code we don’t need is good for many reasons, including security (lowering the attack surface). In this case, we are simply using an alternative strategy useful for many scenarios.

To achieve our new goal, we can extract the construction logic of the ILocationService interface into a factory.

Project – Factory

In the project, a copy from the Strategy project, we start by renaming the InjectAbstractionLocationsController class to LocationsController. We can then delete the other controllers.Now, we want to change the ILocationService bindings to reflect the following logic:

  • When developing the application, we use the InMemoryLocationService class.
  • When deploying to any environment, we must use the SqlLocationService class.

To achieve this, we use the Environment property of the WebApplicationBuilder object. That property of type IWebHostEnvironment contains some useful properties like the EnvironmentName, and .NET adds extension methods, like the IsDevelopment method that returns true when the EnvironmentName equals Development. Here’s the Program.cs file code:

using Factory.Data;
using Factory.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddSingleton<ILocationService>(sp =>
{
    if (builder.Environment.IsDevelopment())
    {
        return new InMemoryLocationService();
    }
    return new SqlLocationService(new NotImplementedDatabase());
});
var app = builder.Build();
app.MapControllers();
app.Run();

The preceding code is fairly straightforward; it registers a delegate to act as a factory, which builds the appropriate service based on the ASP.NET Core Environment.

We are using the new keyword here, but is this wrong? The composition root is where we should create or configure elements, so instantiating objects there is correct, as it is to use the Service Locator pattern. It is best to avoid the new keyword and the Service Locator pattern whenever possible, but using the default container makes it harder than with a full-featured third-party one. Nevertheless, we can avoid doing that in many cases, and even if we must use the new keyword and the Service Locator pattern, we often don’t need a third-party container.

When we run the program, the right instance is injected into the controller based on the logic we added to the factory. The flow is similar to the following:

  1. The application starts.
  2. A client sends an HTTP request to the controller (GET /travel/locations).
  3. ASP.NET Core creates the controller and leverages the IoC container to inject the ILocationService dependency.
  4. Our factory creates the correct instance based on the current environment.
  5. The action method runs, and the client receives the response.

We could also create a factory class and an interface, as explored in the previous chapter. However, in this case, it would likely just create noise.

An essential thing to remember is that moving code around your codebase does not make that code, logic, dependencies, or coupling disappear. Coding a factory doesn’t make all your design issues disappear. Moreover, adding more complexity adds a cost to your project, so factory or not, each time you try to break tight coupling or remove a dependency, ensure that you are not just moving the responsibility elsewhere or overengineering your solution.

Of course, to keep our composition root clean, we could create an extension method that does the registration, like an AddLocationService method. I’ll leave you to try this one out, find other ways to improve the project, or even improve one of your own projects.The possibilities are almost endless when you think about the Factory patterns. Now that you’ve seen a few in action, you may find other uses for a factory when injecting some classes with complex instantiation logic into other objects.

Leave a Reply

Your email address will not be published. Required fields are marked *



         


          Terms of Use | Accessibility Privacy