Loose Coupling and Tight Coupling in C#

Berkay Ergun
4 min readDec 9, 2023

--

Hello, in this article I will explain what loouse and tight coupling is, which is one of the basic principles of software and object oriented programming. Of course, before explaining this, we need to understand what the principle is.

PRINCIPLE

A principle is a principle that sets the moral boundaries of a whole, a phenomenon, a thing. It is universal. What is called a principle is not a rule. Where there are principles, there are rules. Rules may change according to circumstances, time and place. But principles are universal.

If you follow the principles, you are acting right, but if you don’t, we cannot say it is wrong. You cannot benefit from the advantages in the field brought by the principles.

Software principles provide a general coding ethic that allows you to demonstrate the most ideal behavior according to your situation.

And what good will this do us?

When writing code, in the process of building code, if there is a collaboration between your objects, you should design this collaboration as loose coupling instead of tight coupling.

This is what designing our code as Loose Coupling gives us;

  • Flexibility
  • Ease of Maintenance
  • Independent Development
  • Reusability
  • Ease of Testing
  • Modularity and Understandability
  • Parallel Development

Loose Coupling supports writing code in accordance with SOLID principles in software design and good design principles and practices. These advantages greatly simplify maintenance, increase software longevity and make development processes more efficient.

It is worth noting that in OOP designs it is not possible to eliminate dependencies between objects one hundred percent. Therefore, if we cannot eliminate the dependencies between objects, we should smooth them out as much as possible, in other words, we should make these dependencies manageable.

Tight Coupling

“Tight coupling” refers to a situation in software design where two components are tightly coupled. In this case, changes in the internal structure of one class can affect the other class, and the coupling between the two classes is tight.

Example:

We want to write code that logs a message. We have two classes, MessageProcessor and LogManager.

LogManager Class:

public class LogManager
{
public void LogToConsole(string message)
{
Console.WriteLine($"Logging to console: {message}");
}

public void LogToFile(string message)
{
Console.WriteLine($"Logging to file: {message}");
}
}

MessageProcessor Class:

public class MessageProcessorForTightCoupling
{
private LogManager logManager;

public MessageProcessorForTightCoupling()
{
this.logManager = new LogManager();
}

public void ProcessMessage(string message)
{
Console.WriteLine($"Processing message: {message}");
this.logManager.LogToFile($"Message processed: {message}");
}

Program.cs

MessageProcessorForTightCoupling messageProcessor = new MessageProcessorForTightCoupling();
messageProcessor.ProcessMessage("Sample message");

If the MessageProcessor class uses the LogManager class as here, we can say that MessageProcessor is dependent on LogManager.

In this case, MessageProcessor will not be able to fulfill its task without the LogManager class.

In addition, any structural change that may occur in the LogManager class will directly affect the MessageProcessor class and will require the necessary arrangements.

These dependencies bring us serious costs. A change in the dependent class requires a change in the other class. This is a very costly and undesirable situation.

Loose Coupling

Let’s code the same code with Loose Coupling.

ILogger Interface:

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

MessageProcessor Class:

public class MessageProcessor
{
private ILogger logger;

public MessageProcessor(ILogger logger)
{
this.logger = logger;
}

public void ProcessMessage(string message)
{
Console.WriteLine($"Processing message: {message}");
this.logger.Log($"Message processed: {message}");
}

ConsoleLogger Class:

public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine($"Logging to console: {message}");
}
}

FileLogger Class:

public class FileLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine($"Logging to file: {message}");
}
}

In order to eliminate such problems, structural changes should be made to the relationships between classes and these relationships should be provided through abstract classes or interfaces.

Let me have one interface, this will provide the relationship between my classes. MessageProcessor will be connected with ILogger. I can manage independence in the least possible service. I minimize the cost.

With the help of an interface, we create a contract, a contract. So only the related class is derived.

So one day in the future, when the logging method changes, we only need to call whichever logging method we want without a big change in the code :) In the Tight Coupling method, we had to make changes in all classes.

A good design is not one where there is independence, but one where dependencies are controlled.

Thanks.

--

--