The Factory Pattern is a creational design pattern that provides a way to encapsulate object creation. Instead of directly instantiating objects, you delegate the creation process to a factory class. This promotes loose coupling and makes your code more flexible and maintainable.
Delegates in C# are type-safe function pointers that can be used to reference methods. By using delegates within the factory pattern, we can achieve a high degree of flexibility in object creation. We can dynamically choose which object to create based on specific criteria or runtime conditions.
Benefits of the Factory Design Pattern
- Encapsulated Object Creation: The factory shields the client code from the intricate details of object instantiation, leading to cleaner and more understandable code.
- Loose Coupling: By decoupling the client from concrete classes, the factory fosters flexibility and ease of maintenance.
- Promoted Reusability: The factory can be effectively employed across different parts of the application to generate objects of diverse types, streamlining development efforts.
Let me explain with an example.
Step 1. Create an ASP.NET Core Web API Project.
Step 2. Create a Models folder and add a class named Mobile.
public class Mobile
{
public required Guid Id { get; set; }
public required string Name { get; set; }
}
Step 3. Create a Contracts folder and add an interface named IMobileProvider as shown.
public interface IMobileProvider
{
IReadOnlyCollection<Mobile> GetMobiles();
}
Step 4. Create a new folder named Providers. Within this folder, create two C# classes: Apple and Samsung. Both classes should implement the IMobileProvider interface.
public class Apple : IMobileProvider
{
public IReadOnlyCollection<Mobile> GetMobiles()
{
return new List<Mobile>
{
new Mobile { Id = Guid.NewGuid(), Name = "iPhone 16 Pro Max" },
new Mobile { Id = Guid.NewGuid(), Name = "iPhone 16 Pro" },
new Mobile { Id = Guid.NewGuid(), Name = "iPhone 15 Pro Max" },
new Mobile { Id = Guid.NewGuid(), Name = "iPhone 15 Pro" }
};
}
}
public class Samsung : IMobileProvider
{
public IReadOnlyCollection<Mobile> GetMobiles()
{
return new List<Mobile>
{
new Mobile { Id = Guid.NewGuid(), Name = "S24 Ultra" },
new Mobile { Id = Guid.NewGuid(), Name = "S23 Ultra" },
new Mobile { Id = Guid.NewGuid(), Name = "S22 Ultra" },
new Mobile { Id = Guid.NewGuid(), Name = "S20 Ultra" }
};
}
}
Step 5. Register services to the container in the Program.cs file.
using FactoryDesignPatternUsingDelegates.Contracts;
using FactoryDesignPatternUsingDelegates.Providers;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<Func<string, IMobileProvider>>(provider =>
{
return provider?.ToUpper() switch
{
"APPLE" => new Apple(),
"SAMSUNG" => new Samsung(),
_ => throw new ArgumentException("Invalid Provider")
};
});
builder.Services.AddControllers();
var app = builder.Build();
app.UseAuthorization();
app.MapControllers();
app.Run();
This code snippet utilizes Dependency Injection (DI) to register a service that creates different mobile provider objects based on a provided string.
- builder.Services.AddSingleton
- This line registers a service within the dependency injection container.
- builder.Services refers to a collection of services managed by the dependency injection framework.
- AddSingleton is a method that specifies the service will have a single instance throughout the application's lifetime.
- <Func<string, IMobileProvider>>
- This defines the type of service being registered.
- Func<string, IMobileProvider> is a generic delegate type.
- This specific delegate takes a string as input and returns an object implementing the IMobileProvider interface.
- provider => {...}
- This defines the logic for creating the IMobileProvider object.
- The provider parameter represents the dependency injection container itself.
- The arrow (=>) introduces a lambda expression that defines the logic for creating the object.
- provider?.ToUpper() switch
- This part checks the provided string and determines which mobile provider to create.
- provider?.ToUpper() attempts to access the provided string and convert it to uppercase (null check ensures safety).
- The switch statement evaluates the converted string against different cases.
- Cases and Object Creation
- The switch statement has three cases
- "APPLE": If the string is "APPLE" (uppercase), a new Apple object is created.
- "SAMSUNG": Similarly, for "SAMSUNG", a new Samsung object is created.
- _: The underscore represents the default case. If the string doesn't match "APPLE" or "SAMSUNG", an ArgumentException is thrown indicating an invalid provider name.
Step 6. Create a Controllers folder and add an API controller named MobilesController as shown below.
[Route("api/[controller]")]
[ApiController]
public class MobilesController : ControllerBase
{
private readonly Func<string, IMobileProvider> _provider;
public MobilesController(Func<string, IMobileProvider> provider)
{
_provider = provider;
}
[HttpGet("{provider}")]
public IActionResult GetMobiles(string provider)
{
return Ok(_provider?.Invoke(provider).GetMobiles());
}
}
Code Breakdown
- Route and Controller Attributes
- [Route("api/[controller]")]: This attribute defines the base route for the controller. The [controller] token will be replaced with the controller name, resulting in a route like /api/Mobiles.
- [ApiController]: This attribute enables automatic model validation, problem details for HTTP APIs, and other conventions for building web APIs.
- Controller Class
- MobilesController : ControllerBase: This defines a controller class that inherits from ControllerBase, providing access to common features like routing, action methods, and HTTP response handling.
- Dependency Injection
- private readonly Func<string, IMobileProvider> _provider;: This declares a private, read-only field to store a function that takes a string (provider name) and returns an IMobileProvider interface.
- public MobilesController(Func<string,IMobileProvider> provider): This is the constructor that receives the Func<string, IMobileProvider> dependency via dependency injection. The injected function is assigned to the _provider field.
- GET Action Method
- [HttpGet("{provider}")]: This defines a GET HTTP method for the route /api/Mobiles/{provider}, where {provider} is a route parameter.
- public IActionResult GetMobiles(string provider): This is the action method that takes the provider string as a parameter.
- return Ok(_provider?.Invoke(provider).GetMobiles());:
- The _provider?.Invoke(provider) part calls the injected function to get an IMobileProvider instance based on the provided provider name.
- The GetMobiles() method on the obtained IMobileProvider is called to retrieve a list of mobile models.
- The Ok() method returns an HTTP 200 OK response with the retrieved mobile models as the response body.
This approach leverages dependency injection to make the controller more flexible and testable. By injecting the Func<string, IMobileProvider>, different implementations of IMobileProvider can be easily plugged in without modifying the controller code.
Step 7. Test the endpoints.In this example, the application listens to port 5000.
Open a browser and browse http://localhost:5000/api/mobiles/apple.
Output
It has listed all the Apple products.
Similarly, open the browser and browse http://localhost:5000/api/mobiles/samsung.
Output
Conclusion
By using delegates with the factory pattern, we can create more flexible, maintainable, and testable C# applications.
Happy Coding!