Coordinate vs Subordinate Components – Coordination

Part 1: Subordination

Part 2: Coordination

 

In my last post on subordination and coordination, I talked about components that introduce subordination relationships due to creating their dependencies. This post will look at coordination and how a component that defers the creation of its dependencies to some other component results in a coordination relationship. As per the last post here are the dictionary definitions of the two words:

Coordinate

  • Adjective: Equal in rank or importance.
  • Noun: Each of a group of numbers used to indicate the position of a point, line, or plane.
  • Verb: To bring the different elements of (a complex activity or organization) into a harmonious or efficient relationship.

Subordinate

  • Adjective: Lower in rank or position, of less or secondary importance.
  • Noun: A person under the authority or control of another within an organization.
  • Verb: To treat or regard as of lesser importance than something else, make subservient to or dependent on something else.

Further to last time, we see that descriptively coordination means to have elements of equal rank and from an action perspective, it means to bring elements into a harmonious relationship. Conversely, we see that subordination describes elements that are lower in rank and are acting in a relationship where they are subservient or dependent on other elements.

As per the dependency inversion principle, we can change a subordination relationship into a coordination relationship by having components depend on abstractions and not be responsible for creating their own dependencies. By relying on an abstraction each component defers the responsibility of creating its collaborators to someone else. Whether this is another object in the graph instantiating it and passing its dependencies in its constructor or things being wired together in your composition root or using a dependency injection library.

Below are the same components from the last post on subordination but this time each component depends on abstractions and their dependencies are wired up in the Program component. Unlike last time, if you were to now draw out the relationships between these components it would look flatter and more like components of “equal rank or importance” being brought together to do something interesting.

 

 

class Program
{
    static void Main(string[] args)
    {
        ILogger logger = new Logger();
        IThingRepository repository = new ThingRepository(logger);
        IThingService service = new ThingService(repository, logger);

        var shinyThings = service.GetShinyThings();
    }
}
internal class ThingService : IThingService
{
    private readonly IThingRepository _repository;
    private readonly ILogger _logger;

    public ThingService(IThingRepository repository, ILogger logger)
    {
        _repository = repository;
        _logger = logger;
    }

    public ShinyThings GetShinyThings()
    {
        var things = _repository.GetThings();

        var shinyThings = MakeShiny(things);

        _logger.Log("Shiny things were created!!");

        return shinyThings;
    }

    private static ShinyThings MakeShiny(Things things)
    {
        return ShinyThings.From(things);
    }
}

 

In the above example, we can see that ThingRepository and ThingService now depend on abstractions. The ThingRepository class now depends on the interface / abstraction ILogger and ThingService depends on IThingRepository and ILogger. The object graph is then wired up in Program where concrete implementations for each of these abstractions are instantiated and composed together. In this scenario, the Program class is the composition root and is responsible for coordination.

Below is an example using a class that mimics a dependency injection  / service locator library:

 

 

class Program
{
    private static readonly DependencyContainer Container = new DependencyContainer();
    private static readonly IThingService ThingService = Container.Get<IThingService>();

    static void Main(string[] args)
    {
        var shinyThings = ThingService.GetShinyThings();
    }
}
internal class DependencyContainer
{
    private readonly Dictionary<Type, object> _types;

    public DependencyContainer()
    {
        ILogger logger = new Logger();
        IThingRepository repository = new ThingRepository(logger);
        IThingService service = new ThingService(repository, logger);

        _types = new Dictionary<Type, object>
        {
            { typeof(IThingService), service }
        };
    }

    public T Get<T>()
    {
        return (T)_types[typeof(T)];
    }
}

 

In the above example, the responsibility for coordination has moved from Program to the DependencyContainer class. The above example is supposed to be representative of a dependency injection library, feel free to mentally substitute DependencyContainer with wherever you normally wire up your dependencies.

Below is another example of how depending on abstractions increases coordination. This example uses a MessagePrinter class that can take a message as a string, format it and then output it somewhere:

 

public class MessagePrinter
{
    private readonly IMessageOutput _output;
    private readonly IMessageFormatter _formatter;

    public MessagePrinter(IMessageOutput output, IMessageFormatter formatter)
    {
        _output = output;
        _formatter = formatter;
    }

    public void PrintMessage(string message)
    {
        _output.OutputMessage(_formatter.FormatMessage(message));
    }
}

public interface IMessageFormatter
{
    string FormatMessage(string message);
}

public interface IMessageOutput
{
    void OutputMessage(string message);
}

 

The MessagePrinter class depends on the two interfaces IMessageOutput and IMessageFormatter. This allows Program to coordinate different concrete implementations of these interfaces to work with MessagePrinter in order to perform different kinds of behaviour.

 

class Program
{
    static void Main(string[] args)
    {
        var consoleOutput = new ConsoleOutput();
        var upperCaseFormatter = new UpperCaseFormatter();
        var upperCaseConsolePrinter = new MessagePrinter(consoleOutput, upperCaseFormatter);

        upperCaseConsolePrinter.PrintMessage("Hello, world!");

        var fileOutput = new FileOutput();
        var obfuscatedFormatter = new ObfuscatedFormatter();
        var obfuscatedFilePrinter = new MessagePrinter(fileOutput, obfuscatedFormatter);

        obfuscatedFilePrinter.PrintMessage("Hello, world!");

        Console.ReadKey();
    }
}

internal class UpperCaseFormatter : IMessageFormatter
{
    public string FormatMessage(string message)
    {
        return message.ToUpper();
    }
}

internal class ConsoleOutput : IMessageOutput
{
    public void OutputMessage(string message)
    {
        Console.WriteLine(message);
    }
}

internal class ObfuscatedFormatter : IMessageFormatter
{
    public string FormatMessage(string message)
    {
        return string.Concat(message.Select(c => '*'));
    }
}

internal class FileOutput : IMessageOutput
{
    public void OutputMessage(string message)
    {
        File.WriteAllText(@"C:\message.txt", message);
    }
}

 

Using the concrete implementations shown in the code above Program could coordinate the following behaviours:

 

IMessageFormatter IMessageOutput
ConsoleOutput UpperCaseFormatter
ConsoleOutput ObfuscatedFormatter
FileOutput UpperCaseFormatter
FileOutput ObfuscatedFormatter

 

We can see that by not introducing subordination we are better able to achieve coordination and one way to achieve this is by components not creating their dependencies. This doesn’t just apply to classes either, the same can be achieved by pushing dependencies out of methods and accepting them as arguments or by taking a functional approach and composing small functions.

This is nothing new, the dependency inversion principle is well known and has been written about at length but I thought it was interesting to look at it from a language perspective.

Share

Leave a Reply

Your email address will not be published. Required fields are marked *

Post comment