Factory Method Pattern In .Net 8

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace APIApplication.FactoryMethodPattern.Providers
{

    public interface IRouteCalculationFactory
    {
        public IRouteCalculation CreateRouteCalculation();
        public bool IsSupports(string type);
    }
    public class BusRouteFactory : IRouteCalculationFactory
    {
        public IRouteCalculation CreateRouteCalculation()
        {
            return new BusRoute();
        }

        public bool IsSupports(string travellingType)

             => travellingType.Equals("bus", StringComparison.OrdinalIgnoreCase);

    }
    public class BikeRouteFactory : IRouteCalculationFactory
    {
        public IRouteCalculation CreateRouteCalculation()
        {
            return new BikeRoute();
        }

        public bool IsSupports(string travellingType) => travellingType.Equals("bike", StringComparison.OrdinalIgnoreCase);

    }
    public class WalkRouteFactory : IRouteCalculationFactory
    {
        public IRouteCalculation CreateRouteCalculation()
        {
            return new WalkRoute();
        }

        public bool IsSupports(string travellingType) => travellingType.Equals("walk", StringComparison.OrdinalIgnoreCase);

    }
    public interface IRouteCalculation
    {
        int CalculateDistance(string source, string destination);
        int CalculateTime(string source, string destination);
    }
    public class BusRoute : IRouteCalculation
    {
        public int CalculateDistance(string source, string destination)
        {
            return 150;
        }

        public int CalculateTime(string source, string destination)
        {
            return 60;
        }
    }
    public class BikeRoute : IRouteCalculation
    {
        public int CalculateDistance(string source, string destination)
        {
            return 100;
        }

        public int CalculateTime(string source, string destination)
        {
            return 40;
        }
    }
    public class WalkRoute : IRouteCalculation
    {
        public int CalculateDistance(string source, string destination)
        {
            return 150;
        }

        public int CalculateTime(string source, string destination)
        {
            return 200;
        }
    }
}
 public class RouteCalculationFactoryProvider
 {
     private readonly IEnumerable<IRouteCalculationFactory> _routeCalculationFactories;
     public RouteCalculationFactoryProvider(IEnumerable<IRouteCalculationFactory> routeCalculationFactories)
     {
         _routeCalculationFactories = routeCalculationFactories;
     }

     public IRouteCalculationFactory GetFactory(string travellingType)
     {
         var factory = _routeCalculationFactories.FirstOrDefault(f => f.IsSupports(travellingType));
         if (factory == null)
         {
             throw new InvalidOperationException("Unknown travelling type");
         }

         return factory;
     }
 }
using APIApplication.FactoryMethodPattern.Providers;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/[controller]")]
public class RouteCalculatorController : ControllerBase
{
    private readonly RouteCalculationFactoryProvider _factoryProvider;

    public RouteCalculatorController(RouteCalculationFactoryProvider factoryProvider)
    {
        _factoryProvider = factoryProvider;
    }

    [HttpGet("calculate")]
    public IActionResult CalculateRoute(string source, string destination, string travellingType)
    {
        try
        {
            var factory = _factoryProvider.GetFactory(travellingType);
            var routeCalculator = factory.CreateRouteCalculation();

            var distance = routeCalculator.CalculateDistance(source, destination);
            var time = routeCalculator.CalculateTime(source, destination);

            return Ok(new { Distance = distance, Time = time });
        }
        catch (InvalidOperationException ex)
        {
            return BadRequest(ex.Message);
        }
    }
}

How IEnumerable<T> Injection Works in .NET Core

When you register multiple implementations of an interface in the DI container, the framework automatically groups these implementations into an IEnumerable<T> when you inject them.

Step-by-Step Explanation:

  1. Registering Multiple Implementations:
    • In the Program.cs or Startup.cs file, you registered multiple classes (BusRouteFactory, BikeRouteFactory, WalkRouteFactory) that all implement the IRouteCalculatorFactory interface
builder.Services.AddSingleton<IRouteCalculatorFactory, BusRouteFactory>();
builder.Services.AddSingleton<IRouteCalculatorFactory, BikeRouteFactory>();
builder.Services.AddSingleton<IRouteCalculatorFactory, WalkRouteFactory>();
  1. Here, each AddSingleton<IRouteCalculatorFactory, SomeFactory>() call registers a concrete factory type as an IRouteCalculatorFactory service in the DI container.
  2. Resolving IEnumerable<IRouteCalculatorFactory>:
    • When a class (like RouteCalculatorFactoryProvider) requests an IEnumerable<IRouteCalculatorFactory> in its constructor, the DI container:
      • Looks for all the services registered as IRouteCalculatorFactory.
      • Automatically creates an IEnumerable<IRouteCalculatorFactory> and injects it into the constructor.
    csharpCopy codepublic RouteCalculatorFactoryProvider(IEnumerable<IRouteCalculatorFactory> factories) { _factories = factories; }
    • At runtime, _factories will contain all the instances of the BusRouteFactory, BikeRouteFactory, and WalkRouteFactory that were registered.

Why It Works

Practical Implications

Summary

In .NET Core, when you inject an IEnumerable<T> of an interface, the DI container automatically provides all the registered implementations of that interface. This allows you to inject multiple services easily and work with them in a flexible and scalable way. The RouteCalculatorFactoryProvider constructor receives all the factories because they were all registered as services in the DI container.

The Factory Method pattern is a widely used creational design pattern that provides an interface for creating objects in a super-class but allows subclasses to alter the type of objects that will be created. While it offers many benefits, it also comes with some challenges and potential downsides:

1. Increased Complexity

2. Tight Coupling Between Factory and Products

3. Violation of the Open/Closed Principle

4. Difficulty in Managing Multiple Factories

5. Limited Flexibility

6. Potential Performance Overhead

7. Testing Challenges

Summary

While the Factory Method pattern provides a robust way to create objects and promotes the use of interfaces and polymorphism, it comes with challenges such as increased complexity, tight coupling, potential violation of the Open/Closed Principle, and testing difficulties. It’s essential to carefully consider these challenges and evaluate whether the Factory Method pattern is the best fit for your specific use case. In some situations, simpler alternatives like using constructors or static factory methods might be more appropriate.

Leave a comment