简体   繁体   中英

Design pattern: child class calling base class

I have "Handlers" able to trigger "Brokers" (an object that does something - not important here).

Handlers are listening to different kind of events:

  • TimeEvent: Every 10 seconds, 10 minutes (...)
  • FileSystemEvent: Once a file copied/moved/deleted
  • DbEvent: When a record is added to a DB table
  • MailEvent: When I received an email in my Office 365 mailbox

Each handler must have:

  • Start and stop methods (start/stop catching events)
  • An instance of the associated broker
  • A way to "trigger" the broker (Process method + specific set of arguments).

Each handler should

  • Trigger the associated broker when a specific event is raised

I want to trigger brokers from the base Handler class so I centralize my logic (fire events, catch exception, manage threads etc.). However, the base handler doesn't know how to call the broker (when to call this function, what parameters to send to the Process method) >> Only specialized children handlers know how to do that.

The only way I found is to implement in the base handler an Execute method accepting an Action parameter... I don't like this approach as it's not really straight forward (child needs to call base class otherwise nothing happens). I was hoping to find a better design to handle this. In addition I can tell you my developers will tell me they don't understand how to use the system.

abstract class Handler : IHandler
{
    public IBroker Broker { get; protected set; }

    public event ProcessedEventHandler Processed;
    protected void OnProcessed(ProcessExecutionResult result) => Processed?.Invoke(this, result);

    public static IHandler Create(IBroker broker)
    {
        if (broker is ITimerTriggeredBroker)
            return new TimeHandler((ITimerTriggeredBroker)broker);
        return null;
    }

    protected Handler(IBroker broker)
    {
        if (broker == null) throw new ArgumentNullException(nameof(broker));
        Broker = broker;
    }

    public abstract void Start();
    public abstract void Stop();

    protected void Execute(Action action)
    {
        var res = new ProcessExecutionResult();
        try
        {
            action?.Invoke();
            res.IsSuccess = true;
        }
        catch (Exception ex)
        {
            res.Exception = ex;
            res.IsSuccess = false;
        }
        finally
        {
            OnProcessed(res);
        }
    }
}

TimeHandler (handling Time related events)

class TimeHandler : Handler
{
    private readonly Timer _timer;
    private readonly DateTime _start;
    private readonly TimeSpan _frequency;

    public TimeHandler(ITimerTriggeredBroker broker)
        : base(broker)
    {
        _start = broker.Trigger.StartTime;
        _frequency = broker.Trigger.Frequency;
        _timer = new Timer(_ => Execute(broker.Process));
    }
 (...)
}

FileHandler (handling FileSystem related events)

class FileHandler : Handler
{
    private readonly FileSystemWatcher _watcher = new FileSystemWatcher();

    public FileHandler(IFileTriggeredBroker broker)
        : base(broker)
    {
        if (!Directory.Exists(broker.Trigger.DirectoryPath))
            throw new DirectoryNotFoundException("Unable to locate the supplied directory");

        _watcher.Filter = broker.Trigger.Filter;
        _watcher.Path = broker.Trigger.DirectoryPath;
        _watcher.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.DirectoryName |
                                NotifyFilters.FileName;

        _watcher.Created += (s, a) =>
        {
            if (IsCopied(a.FullPath)) Execute(() => broker.Process(a.FullPath));
        };
    }

There are several aspects to what you are trying to achieve:

  1. The architecture should be easy to understand and follow by your programmers. It should guide them while they program and protect them from making errors.

  2. It should be robust. For example, you should guarantee that every file created in the watched folder is processed.

In my answer I will ignore the robustness aspect. Please look at this seriously. FileSystemWatcher is not guaranteed to deliver all files created. Also, it is advisable to you separate the processing of FileSystemWatcher events in a separate thread or use .NET tasks for this.

Furthermore, I think you should consider using a queue, like Microsoft MQ, Azure Queue, RabbitMQ. You can do this directly or use a system like MassTransit.

Below I propose an architecture that will make it easier for your programmers to build upon.

General description

Divide the application in folders or different assemblies to make a clear separation between framework and specific handlers/brokers.

解

For each type of processing we create a specific message class and let the handler and broker implement a generic interface specific for the message type.

We will make use of the C# advanced type system to make sure that it is difficult to make mistakes and that the compiler will help the programmers to use the right things. For this we use generic interfaces and classes based on the message type class.

Main program

Here we will setup a manager that will register all handlers and brokers with their respective messages. This is a stand alone example, I suggest you use a system for dependency injection like for example AutoFac to further optimize this.

static void Main(string[] args)
{
    var manager = new Manager();
    manager.Register<FileHandlerMessage>(new FileHandler(), new FileBroker());
    manager.Register<TimeHandlerMessage>(new TimeHandler(), new TimeBroker());

    manager.Start();

    Console.ReadLine();

    manager.Stop();
}

Manager

The role of the Manager class is to organize for proper usage of handlers and brokers.

class Manager
{
    private List<IGenericHandler> handlers = new List<IGenericHandler>();

    public void Register<M>(IHandler<M> handler, IBroker<M> broker) where M : Message
    {
        handlers.Add(handler);
    }

    public void Start()
    {
        foreach ( var handler in handlers )
        {
            handler.Start();
        }
    }
    public void Stop()
    {
        foreach (var handler in handlers)
        {
            handler.Stop();
        }
    }
}

Messages

For each type of broker, we will define a specific message class, derived from a common base class:

abstract class Message
{
}

class FileHandlerMessage : Message
{
    public string FileName { get; set; }
}

Handlers

interface IGenericHandler
{
    void Start();
    void Stop();
}

interface IHandler<M> : IGenericHandler where M : Message
{
    void SetBroker(IBroker<M> broker);
}

class FileHandler : IHandler<FileHandlerMessage>
{
    private IBroker<FileHandlerMessage> broker;

    public FileHandler()
    {
    }

    public void SetBroker(IBroker<FileHandlerMessage> fileBroker)
    {
        this.broker = fileBroker;
    }

    public void Start()
    {
        // do something
        var message = new FileHandlerMessage();
        broker.Process(message);
    }

    public void Stop()
    {
        // do something
    }
}

class TimeHandler : IHandler<TimeHandlerMessage>
{
    private IBroker<TimeHandlerMessage> broker;

    public void SetBroker(IBroker<TimeHandlerMessage>  broker)
    {
        this.broker = broker;
    }
    public void Start()
    {
        // do something
        var message = new TimeHandlerMessage();
        broker.Process(message);
    }

    public void Stop()
    {
        // do something
        throw new NotImplementedException();
    }
}

Brokers

class FileBroker : IBroker<FileHandlerMessage>
{
    public void Process(FileHandlerMessage message)
    {
        throw new NotImplementedException();
    }
}

class TimeBroker : IBroker<TimeHandlerMessage>
{
    public void Process(TimeHandlerMessage message)
    {
        throw new NotImplementedException();
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM