DDan
DDan

Reputation: 8276

Introduce "Mandatory" Methods to Implementors of an Interface

The current implementation is pretty much aligns with a simple Strategy Design Pattern. There are multiple steps to be executed and these will be invoked provided by the following interface:

public interface ICommandStep
{
    void Execute(CommandParameters cParams);
    string StepName { get; }
}

Example implementor:

public class Step1 : ICommandStep {
    public string StepName => "Initial Step";
    public void Execute(CommandParameters cParams) {
        // instructions
    }
}

By now there many different classes implementing this interface, and I want to ensure all of them will have a pre and post step to execution. For example log state, params, StepName start and end.

How could I introduce a way to have protected virtual void PreExecute and protected virtual void PostExecute methods with common (overridable) logic and to make sure the method will always be invoked in this order:

1. PreExecute
2. Execute
3. PostExecute

Preferably without changing the Execute method in the implementor classes.

Introducing abstract class is possible.

Upvotes: 1

Views: 89

Answers (3)

Krzysztof Błażełek
Krzysztof Błażełek

Reputation: 893

I can think of 2 solutions:

  1. Template Method pattern as proposed by Backs.
  2. Decorator Pattern together with factory. Decorator pattern is great when you want to execute some code before/after call to other object. Factory is to ensure that each created object will be decorated.

Example implementation of 2.:

using System;

namespace ConsoleApplication6
{
    /// <summary>
    /// This is the component in Decorator Pattern
    /// </summary>
    public interface ICommandStep
    {
        void Execute(String cParams);
        string StepName { get; }
    }

    /// <summary>
    /// This is the concrete component in Decorator Pattern
    /// </summary>
    public class ConcreteStep1 : ICommandStep
    {
        public string StepName
        {
            get
            {
                return "1";
            }
        }

        public void Execute(string cParams)
        {
            Console.WriteLine($"STEP {StepName}: EXECUTE");
        }
    }

    /// <summary>
    /// This is the decorator in Decorator Pattern
    /// </summary>
    public abstract class StepDecorator : ICommandStep
    {
        protected ICommandStep _commandStep;
        public abstract string StepName
        {
            get;
        }
        public StepDecorator(ICommandStep commandStep)
        {
            this._commandStep = commandStep;
        }
        public abstract void Execute(string cParams);
    }

    /// <summary>
    /// This is the concrete decorator in Decorator Pattern
    /// </summary>
    public class ConcreteStepDecorator : StepDecorator
    {
        public ConcreteStepDecorator(ICommandStep commandStep) : base(commandStep) { }

        public override string StepName
        {
            get
            {
                return _commandStep.StepName;
            }
        }

        public override void Execute(string cParams)
        {
            // You can do whatever you want before / after execution of command
            Console.WriteLine($"STEP {_commandStep.StepName}: PRE EXECUTE");
            _commandStep.Execute(cParams);
            Console.WriteLine($"STEP {_commandStep.StepName}: POST EXECUTE");
        }
    }

    /// <summary>
    /// This is a Simple Factory. You encapsulate here creation of ICommandStep, so that it will always be decorated
    /// </summary>
    public class SimpleStepFactory
    {
        public ICommandStep createStep()
        {
            return new ConcreteStepDecorator(new ConcreteStep1());
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            var step = new SimpleStepFactory().createStep();
            step.Execute("params");
            Console.ReadLine();
        }
    }
}

This solution has couple of advantages:

  • You can wrap your steps with many decorators, i.e. you can wrap step in decorator that shows it's name and wrap these two in another decorator that shows the parameters
  • You don't have to modify your steps, because all the extra work is handled by decorators

Upvotes: 0

Dai
Dai

Reputation: 155400

Adding new methods to the interface will necessarily mean breaking the existing implementations. If implementation is optional then you should use an abstract class instead of an interface.

An alternative exists by adding a new interface and adding a runtime type-check in an extension method, like so:

public interface ICommandStep
{
    void Execute(CommandParameters cParams);
    string StepName { get; }
}

public interface ICommandStep2 : ICommandStep
{
    void PreExecute()
    void PostExecute()
}

public static class Extensions
{
    public static void Execute2(this ICommandStep step, CommandParameters cParams)
    {
        if( step is ICommandStep2 step2 )
        {
            step2.PreExecute();
            step2.Execute( cParams );
            step2.PostExecute();
        }
        else
        {
            step2.Execute( cParams );
        }
    }
}

Usage:

ICommandStep[] steps = ...
foreach( ICommandStep step in steps )
{
    step.Execute2( cmdParams );
}

Upvotes: 0

Backs
Backs

Reputation: 24913

You can declare base class and define overridable methods in needed order:

public interface ICommandStep
{
    void Execute(CommandParameters cParams);
    string StepName { get; }
}

public abstract class CommandBase : ICommandStep
{
    public void Execute(CommandParameters cParams)
    {
        PreExecute();
        ExecuteInternal(cParams);
        PostExecute();
    }

    protected virtual void PostExecute()
    {
    }

    protected virtual void ExecuteInternal(CommandParameters cParams)
    {
    }

    protected virtual void PreExecute()
    {
    }

    public abstract string StepName { get; }
}

public class Step1 : CommandBase
{
    public override string StepName => "Initial Step";
    protected override void ExecuteInternal(object cParams)
    {
        // instructions
    }
}

Upvotes: 3

Related Questions