dimanche 31 juillet 2016

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));
        };
    }

Aucun commentaire:

Enregistrer un commentaire