Alternative to Template Pattern where the implementation can have an attribute

Viewed 37

I'm using a series of Template Pattern classes that represent different types of events.

internal abstract class DayEndingEvent : Event
{  
    internal void OnDayEnding(object? sender, DayEndingEventArgs e)
    {
        if (IsHooked) OnDayEndingImpl(sender, e);
    }

    protected abstract void OnDayEndingImpl(object? sender, DayEndingEventArgs e);
}

This pattern ensures that the implementation only runs if the event is "hooked", which allows other parts of the application to activate/deactivate the event by calling Hook and Unhook methods from the base Event class.

internal abstract class Event
{
    public bool IsHooked {get; private set;}

    public bool Hook() => !IsHooked && (IsHooked = true);

    public bool Unhook() => IsHooked && !(IsHooked = false);
}

(Event is obviously more complex than this, but this is enough to get the picture). My EventManager can instantiate one of every implementation of this pattern and hook their OnDayEnding to the appropriate handler in an external API.

This has worked fine for a while, but now I have a new requirement to add prioritization to these classes. The only way to do so (and this is a limitation of the external API) is by adding attribute [EventPriority] to the event callback. But obviously I can't annotate OnDayEnding with a priority since that would set the priority of all implementations, which defeats the whole purpose.

The attribute will have no effect anywhere else but on the callback. The only other solution I can see is to remove the Impl and just make the callback itself abstract. But that means I'd have to manually check the IsHooked flag on every implementation, which is what I want to avoid.

So question is, can anybody sugest an alternative to this pattern that would both 1) allow me to have different implementations of the callback, to which I can add priority attributes, and 2) enforce the check for IsHooked?

1 Answers

There are two possibilities I have come across recently when I encountered a similar problem:

Option one, have an entry method that has the required attributes:

public class SpecificImplementationClass1 : BaseClass, IInitializer 
{
    [SomeAttribute]
    public void CallMeToInitiate(SomeType input)
    {
        ExecuteCommonCode(input);
    }

    protected override void ExecuteSpecificCode(object input)
    {
        var typedInput = (SomeType) input;
        // ...execute whatever implementation-specific code here
    }
}

public class BaseClass
{
    protected void ExecuteCommonCode(object input)
    {
         // DoSomethingBefore(input);
         ExecuteSpecificCode(input);
         // DoSomethingAfter(input);
    }

    protected abstract void ExecuteSpecificCode(object input);
}

public interface IInitializer
{
    void CallMeToInitialize(SomeType input);
}

// Get all IInitializers through dependency injection and call "CallMeToInitialize(new SomeType())" on each

Option two, use the template delegate pattern

Related