Skip to content
C#DotNetDependency InjectionIoCInversion of ControlDependency Inversion PrincipleDIPSOLID Principles

From Tightly Coupled to Loosely Coupled Classes

October 6, 20262 min read
Share:

🔴 Tightly Coupled Classes

👉 Step 1

C#
public class FileLogger
{
    public void Log(string message)
    {
        Console.WriteLine("File Log: " + message);
    }
}

public class CustomerService
{
    private FileLogger logger = new FileLogger(); // ❌ tightly coupled

    public void AddCustomer(string name)
    {
        logger.Log($"Customer {name} added.");
    }
}

//Program.cs 
service.AddCustomer("Hridu");

❌ Problem: CustomerService is tightly coupled to FileLogger. Cannot change the logger easily.

⚙️ Step 2: Implement IoC using Factory Pattern

Control object creation outside the main class.

C#
public class FileLogger
{
    public void Log(string message)
    {
        Console.WriteLine("File Log: " + message);
    }
}
public class LoggerFactory
{
    public static FileLogger CreateLogger()
    {
        return new FileLogger();
    }
}

public class CustomerService
{
    private FileLogger logger;

    public CustomerService()
    {
        logger = LoggerFactory.CreateLogger(); // ✅ IoC via factory
    }

    public void AddCustomer(string name)
    {
        logger.Log($"Customer {name} added.");
    }
}

//Program.cs 
service.AddCustomer("Hridu");

✅ Better — object creation is delegated to a factory, not hardcoded.


📐 Step 3: Implement DIP by creating abstraction

C#
public interface ILogger
{
    void Log(string message);
}

public class FileLogger : ILogger
{
    public void Log(string message)
    {
        Console.WriteLine("File Log: " + message);
    }
}

public class CustomerService
{
    private ILogger logger; //Now CustomerService depends on abstraction, not implementation:

    public CustomerService()
    {
        logger = new FileLogger(); // ❌ still manually creating (but using abstraction now)
    }

    public void AddCustomer(string name)
    {
        logger.Log($"Customer {name} added.");
    }
}

✅ DIP applied: High-level class depends on ILogger, not FileLogger.


🔄 Step 4: Implement DI (Dependency Injection)

C#
public class CustomerService
{
    private readonly ILogger logger;

    // ✅ Constructor Injection
    public CustomerService(ILogger logger)
    {
        this.logger = logger;
    }

    public void AddCustomer(string name)
    {
        logger.Log($"Customer {name} added.");
    }
}

// Usage:
ILogger logger = new FileLogger();
CustomerService service = new CustomerService(logger);
service.AddCustomer("Hridu");

✅ Class no longer creates the dependency — it receives it. This makes it testable, flexible, and decoupled.


✅ Step 5: Use IoC Container (e.g., .NET Core)

📦 Register in Program.cs (or Main method for console app):

C#
using Microsoft.Extensions.DependencyInjection;

var services = new ServiceCollection();

services.AddScoped<ILogger, FileLogger>();       // Register abstraction and implementation
services.AddScoped<CustomerService>();           // Register service

var provider = services.BuildServiceProvider();

var service = provider.GetRequiredService<CustomerService>();
service.AddCustomer("Hridu");

✅ You now use an IoC container to manage dependencies — full Inversion of Control.


🟢 Result: Loosely Coupled Classes

✅ FeatureAchieved By
No hardcoded dependenciesIoC / DI
Easy to change logger typesDIP + Interface
Testable with mock loggersDI
Pluggable and extendableIoC container