28 Oct 2023



Intermediate

Examples of Inversion of Control (IoC)

Example 1:

Analogy to help understand IoC:

Imagine a restaurant. The customer does not need to know how to cook the food, or even what ingredients are needed. They simply place an order with the waiter, and the waiter takes care of the rest. The customer is decoupled from the cooking process, and they can focus on enjoying their meal.

In an IoC application, the container or framework is like the waiter. It takes care of creating and managing the dependencies of the objects in the application. The objects themselves simply focus on their core responsibility. This makes the application more loosely coupled and testable.

IoC is a powerful design principle that can be used to improve the quality of software. It is often used in conjunction with other design patterns, such as dependency injection, to create flexible and maintainable applications.

Certainly, here's a simplified pseudo-code example in C# ASP.NET Core to demonstrate Inversion of Control (IoC) and Dependency Injection:

// Define a service interface
public interface IFoodService
{
    void ServeFood();
}

// Create a concrete implementation of the service
public class Chef : IFoodService
{
    public void ServeFood()
    {
        // Chef's logic to cook and serve food
        Console.WriteLine("Chef: Food is ready!");
    }
}

// Create a controller in ASP.NET Core that relies on IFoodService using IoC
[Route("api/restaurant")]
[ApiController]
public class RestaurantController : ControllerBase
{
    private readonly IFoodService _foodService;

    // IoC: Inject IFoodService into the RestaurantController
    public RestaurantController(IFoodService foodService)
    {
        _foodService = foodService;
    }

    [HttpGet("place-order")]
    public IActionResult PlaceOrder()
    {
        // Restaurant's logic to take customer's order
        Console.WriteLine("Customer: I'd like a meal, please.");

        // Let the IFoodService handle cooking and serving
        _foodService.ServeFood();

        Console.WriteLine("Customer: Enjoying the meal.");
        return Ok("Enjoy your meal!");
    }
}

// Set up dependency injection in Startup.cs
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // Register Chef as the implementation of IFoodService
        services.AddScoped<IFoodService, Chef>();
    }

    // Other configuration methods...
}

// Main program
public static void Main(string[] args)
{
    var host = CreateHostBuilder(args).Build();
    host.Run();
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        });
}

In this pseudo-code, we have defined an IFoodService interface, a Chef class that implements it, and a RestaurantController in ASP.NET Core that relies on IFoodService using Inversion of Control. The Chef is registered as the implementation of IFoodService in the Startup class. When a customer places an order by accessing the PlaceOrder endpoint in the RestaurantController, the IoC container injects the appropriate IFoodService implementation (in this case, Chef), demonstrating how IoC helps in making the application more loosely coupled.


Example 2:

Scenario: Starting a Car remotely through an API call

Imagine you are building a modern car management system in an ASP.NET Core WebAPI application. The system allows users to start their cars remotely through an API call. To implement this, you use the IoC and Dependency Injection principles:

  1. Car Class: The Car class represents a car in the system. It has a complex set of features, including engine management. However, it doesn't directly manage the engine itself. Instead, it relies on an Engine component for starting and stopping the engine.

  2. Engine Class: The Engine class is responsible for managing the car's engine. It has a Start() method that handles the engine start process.

  3. IoC Container: In this system, the IoC container is represented by ASP.NET Core's built-in dependency injection. The system is configured to know how to create instances of the Car and Engine classes and manage their dependencies.

  4. CarsController: This is an API controller that exposes an endpoint /api/cars/start. When a user sends a request to this endpoint, the StartCar action is executed. It injects a Car instance and calls the Start() method on the Car, which, in turn, starts the engine.

User Interaction:

  1. A user accesses your car management system's API via a web or mobile application.
  2. The user sends a POST request to /api/cars/start, indicating that they want to start their car remotely.

System Interaction:

  1. The ASP.NET Core application processes the user's request.
  2. The CarsController is invoked and creates an instance of the Car class via the IoC container.
  3. The Car instance, in turn, relies on the Engine to start the car.
  4. The Engine class executes its Start() method to start the engine.

Outcome:

  • The car's engine is successfully started, allowing the user to use their car remotely.

In this scenario, the IoC and Dependency Injection principles help manage the complex relationship between the car and its engine. The system is highly modular and testable, and you can easily replace components or add new features without impacting the rest of the system.

Here's a pseudo code example of how you can implement the given IoC example in a C# ASP.NET Core WebAPI application:

// Car class
public class Car
{
    private readonly IEngine engine;

    public Car(IEngine engine)
    {
        this.engine = engine;
    }

    public void Start()
    {
        engine.Start();
    }
}

// Engine interface
public interface IEngine
{
    void Start();
}

// Engine class
public class Engine : IEngine
{
    public void Start()
    {
        // Engine start logic
    }
}

// IoC container using built-in ASP.NET Core dependency injection
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        // Register the Engine and Car classes with the IoC container
        services.AddTransient<IEngine, Engine>();
        services.AddTransient<Car>();
    }
}

// Controller to use the Car class
[Route("api/cars")]
[ApiController]
public class CarsController : ControllerBase
{
    private readonly Car car;

    public CarsController(Car car)
    {
        this.car = car;
    }

    [HttpGet("start")]
    public IActionResult StartCar()
    {
        car.Start();
        return Ok("Car started");
    }
}

// Main program
public static void Main(string[] args)
{
    var host = CreateHostBuilder(args).Build();
    host.Run();
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        });
}

In this example:

  1. The Car class and Engine class are defined, but we've introduced an IEngine interface to make it easier for dependency injection.

  2. The IoCContainer is replaced with the built-in dependency injection container provided by ASP.NET Core. In the Startup class, we configure services and register Engine and Car with the IoC container.

  3. In the CarsController, we inject the Car class as a dependency. When the /api/cars/start endpoint is accessed, the StartCar action is executed, and it calls the Start method on the injected Car instance, demonstrating how IoC works with ASP.NET Core's built-in dependency injection.

solid-principles
inversion-of-control
example