Design Patterns – Coordinating Loosely Coupled Classes with Strong Interdependencies in C#

abstract-factorybuilder-patterncdesign-patternsfactory

I have a collection of cooperative classes whose behaviors are interdependent upon one another. But I wish to keep them loosely coupled, so I've created appropriate interfaces.

I want to determine an appropriate pattern to instantiate specific implementations of these objects.

Here's an outline of their interdependencies:

  • IService : IDisposable: listens for messages; exposes a Listen method which:
    • calls IMessageClient.GetNextMessage iteratively
    • invokes a (delegate which creates a?) new IMessageHandler instance in a new thread for each message
    • up to NumberOfThreads concurrent threads
  • IServiceMonitor<IService>: monitors the service:
    • exposes Start method which invokes the IService.Listen()
    • exposes Stop method which disposes IService
    • exposes Pause and Resume methods which respectively zero or reset the IService.NumberOfThreads
    • calls CreateRemoteConfigClient() to get a client every 90 seconds, then IRemoteConfigClient.GetConfig
    • notifies any configuration changes to IMessageClient, IService, and any subsequent IMessageHandler
  • IMessageClient : IDisposable; exposes GetNextMessage which:
    • long polls a message queue for the next request
  • IMessageHandler : IDisposable; exposes HandleMessage which:
    • does something with the message, requesting on the way further IXyzClients from the IFactory to access other services
  • IRemoteConfigClient : IDisposable; exposes GetConfig which:
    • retrieves any remote overrides of the current configuration state

This has led me to create:

  • IFactory; with the following members:
    • CreateMonitor: returns a new IServiceMonitor<IService>
    • GetService: returns the IService created to accompany the most recent IServiceMonitor, or a new IService
      • NB: a Service should be able to be obtained without a Monitor having been created
    • CreateMessageClient: returns a new IMessageClient
    • Either:
      • CreateMessageHandler: returns a new IMessageHandler
      • MessageHandlerDelegate: creates a new IMessageHandler and invokes HandleMessage
    • CreateRemoteConfigClient: returns a new IRemoteConfigClient

Implementations of the core interfaces accept the IFactory in their constructors.
This is so that:

  • IService can call CreateMessageClient() to get a single IMessageClient which it will Dispose when it's done
  • IServiceMonitor can call GetService() to allow it to coordinate and monitor the IService
  • IMessageHandler can report its progress back via IMessageClient

IFactory, of course, started out ostensibly as an implementation of the Factory pattern, then it began to lean more towards a Builder pattern, but in reality none of those feel right.
I'm Create-ing some objects, Get-ting others, and certain things, like the fact that a subsequent call to CreateMonitor will modify the result of GetService, just feel wrong.

What's the right naming convention for a class which co-ordinates all these others, and IS there an actual pattern that can be followed, am I over-engineering, or am I over-analyzing?!

Best Answer

You could try the Mediator pattern. We used it a couple of times.

For example:

// interface for all "Collegues" 
public interface IColleague
{
    Mediator Mediator { get; }
    void OnMessageNotification(MediatorMessages message, object args);
}
public enum MediatorMessages
{
    ChangeLocale,
    SetUIBusy,
    SetUIReady
}
public class Mediator
{
    private readonly MultiDictionary<MediatorMessages, IColleague> internalList =
        new MultiDictionary<MediatorMessages, IColleague>(EnumComparer<MediatorMessages>.Instance); //contains more than one value per key
    public void Register(IColleague colleague, IEnumerable<MediatorMessages> messages)
    {
        foreach (MediatorMessages message in messages)
            internalList.AddValue(message, colleague);
    }
    public void NotifyColleagues(MediatorMessages message, object args)
    {
        if (internalList.ContainsKey(message))
        {
            //forward the message to all listeners
            foreach (IColleague colleague in internalList[message])
                colleague.MessageNotification(message, args);
        }
    }

    public void NotifyColleagues(MediatorMessages message)
    {
        NotifyColleagues(message, null);
    }
}

And now the collegues (or controllers in our case implemented it like this):

public class AddInController : IColleague
{
    public Mediator Mediator
    {
        get { return mediatorInstance; }
    }

    public AddInController()
    {
        Mediator.Register(this, new[]
                                    {
                                        MediatorMessages.ChangeLocale,
                                        MediatorMessages.SetUIBusy,
                                        MediatorMessages.SetUIReady
                                    });
    }

    public void OnMessageNotification(MediatorMessages message, object args)
    {
        switch(message)
        {
            case MediatorMessages.ChangeLocale:
                UpdateUILocale();
                break;
            case MediatorMessages.SetUIBusy:
                SetBusyUI();
                break;
            case MediatorMessages.SetUIReady:
                SetReadyUI();
                break;
        }
    }
}
Related Topic