Example of Loose Coupled Code
Using Delegates and Interfaces

If you are not sure what it means to have loose and tight coupled code, I recommend reading my other post about it here. In that post, I talk about what it is and why we want it.

I am using C#, but the design principles should be useable in other languages as long as they have their version of delegates and interfaces. We will make a simple console app in Visual Studio with a network class responsible for talking with other systems. This example could easily be something you might do someday.

Sending Messages (interface)

Let’s start with sending messages. In our Network class, we have a send method that takes a string. To send a message, we need to create a Network object and call the send method. In the code below, you can see how Program is tightly coupled with Network. Program has multiple references to the Network class, and we can’t replace it without changing variable types.

using System;

public class Program
{
    static Network network;
    static void Main(string[] args)
    {
        network = new Network();
        network.Send("Hi");
        Console.ReadLine();
    }
}

public class Network
{
    public void Send(string message)
    {
    }
}

We can fix this by using an interface. First, create an INetworkable interface that requires a send method just like the one Network already had and make Network use it. Now in Program we can change the variable to INetworkable. Program still knows about the Network class, but it is limited to a single line.

In the code below, I have also added some more network classes to demonstrate how we can easily replace what kind of network communication we want to use just by changing line 8. We don’t need to fix other lines because the rest of our code does not care what class we are using; they only require that it has the interface. You can achieve the same by making Network inherit from an abstract class with an abstract send method.

using System;

public class Program
{
    static INetworkable network;
    static void Main(string[] args)
    {
        network = new NetworkTCP();
        network.Send("Hi");
        Console.ReadLine();
    }
}

public interface INetworkable
{
    public void Send(string message);
}

public class NetworkTCP : INetworkable
{
    public void Send(string message)
    {
    }
}
public class NetworkUDP : INetworkable
{
    public void Send(string message)
    {
    }
}
public class NetworkWebRTC : INetworkable
{
    public void Send(string message)
    {
    }
}

Receiving Messages (delegates)

The next thing we want is to be able to receive messages as well. Below is some code where Network wants to tell Program that it has received a message saying “Hi back” every second. I have left out the send code for now.

Now the code is tightly coupled again because Network needs to know about Program to call its ReceiveMessage method. We cant reuse Network in other projects because it breaks without Program. We also have to update Network if we change the ReceiveMessage method name.

using System;
using System.Threading;

public class Program
{
    static void Main(string[] args)
    {
        new Network();
        Console.ReadLine();
    }

    public static void ReceiveMessage(string message)
    {
        Console.WriteLine(message);
    }
}

public class Network
{
    public Network()
    {
        new Thread(() =>
        {
            while (true)
            {
                Thread.Sleep(1000);
                Program.ReceiveMessage("Hi back");
            }
        }).Start();
    }
}

We can fix this by having Network take an action delegate in its constructor. When we receive a message, we invoke the action instead of calling Program directly. When we create the Network object, we pass it the ReceiveMessage method. The code below behaves the same as before, but now Network does not know about Program anymore. It only cares about the method passed to it.

using System;
using System.Threading;

public class Program
{
    static void Main(string[] args)
    {
        new Network(ReceiveMessage);
        Console.ReadLine();
    }

    public static void ReceiveMessage(string message)
    {
        Console.WriteLine(message);
    }
}

public class Network
{
    public Network(Action<string> receiveMessageCallback)
    {
        new Thread(() =>
        {
            while (true)
            {
                Thread.Sleep(1000);
                receiveMessageCallback.Invoke("Hi back");
            }
        }).Start();
    }
}

Final Result and Remark

Below is the final result when we merge our send and receive code. You can remove the Program class without getting any missing reference errors. If you remove the Network class, you only get one error on line 8, which can be fixed easily by using another class.

It might seem silly to do all this extra work for 42 lines of code, but when you begin to have 1000+ lines across multiple files written by different developers, you will appreciate the additional time you spend at the beginning.

You can do a lot more to have loosely coupled code, but this should be enough to help you get started.

using System;
using System.Threading;

public class Program
{
    static INetworkable network;
    static void Main(string[] args)
    {
        network = new Network(ReceiveMessage);
        network.Send("Hi");
        Console.ReadLine();
    }

    static void ReceiveMessage(string message)
    {
        Console.WriteLine("Received: " + message);
    }
}

public interface INetworkable
{
    public void Send(string message);
}

public class Network : INetworkable
{
    public Network(Action<string> receiveMessageCallback)
    {
        new Thread(() =>
        {
            while (true)
            {
                Thread.Sleep(1000);
                receiveMessageCallback.Invoke("Hi back");
            }
        }).Start();
    }

    public void Send(string message)
    {
        Console.WriteLine("Send: " + message);
    }
}

Cover photo by Bill Oxford on Unsplash