Giannis Grivas
Giannis Grivas

Reputation: 3412

How to share the same context between commands in Command-Pattern with C#?

I've implemented the command pattern (in a multi-support way) in my application.

Structure:

class MultiCommand : BaseCommand

abstract class BaseCommand : ICommand

Process Flow:

   var commandsGroup = new MultiCommand(new List<ICommand>()
            {
                new Command1(),
                new Command2(),
                new Command3(),
            });

   commandsGroup.Execute()

Now, suppose that in Command1 a somethingID is changed and I'll use this new value in Command2... And also, that there are plenty of other properties and objects that are being affected during the whole execution process.

Also, there are some interface implementations that should be available at any command just using the context object like:

Context.ServerController.something();

The instantiation of the IServerController will take place just before the multiCommandGroup initialization.

How can I have a shared context like this for all Commands of the group?

Example of the Context class:

public class CommandContext
{
    public IServerController ServerController;
    public RequiredData Data { get; set; }

    public CommandContext(){}
}

IMPORTANT A minimal implementation Code is here

Upvotes: 9

Views: 3004

Answers (7)

Khanh TO
Khanh TO

Reputation: 48982

In your case, going with injecting context through constructor is fine as mentioned by others. But in general, I would go with injecting the context through method parameters instead:

public class Command1: BaseCommand
{
    //inject as parameter instead
    public void Execute(Context ctx)
    {

    }
}

The reasons are:

  • The context should be managed by CommandGroup so that we have better encapsulation.
  • The CommandGroup is responsible for executing its list of commands so that it's possible for the CommandGroup to pass to each Command only the parameters each Command really needs, these parameters may be constructed at runtime (maybe by previous Commands) so that it's not possible to pass in these objects as the time we construct the list of commands. Therefore, it's easier to reuse Command and also simplify unit testing these Commands as we don't need to construct the whole context object in unit tests.

Maybe you don't need to care about these things at the moment, but method injection gives more flexibility. If you have worked with some frameworks in .NET, you would see something similar like OwinContext, FilterContext,.. they are passed as parameters and contain relevant information for that context.

In my opinion, your case is not a good fit for Command pattern. A Command represents a user request (action) and these objects could be created dynamically at runtime, but you're predefining your Commands at coding time.

What you're trying to do looks like owin middleware or asp.net web api message handler which are http://www.dofactory.com/net/chain-of-responsibility-design-pattern

Upvotes: 3

Shane Lu
Shane Lu

Reputation: 1104

My 0.02:

1) The MultiCommand class looks like a Composite pattern.

You may want to add a GetParentCommand() method at the base command class and add an AddChildCommand() method at the MultiCommand class, which set every children's parent.

Then the children commands could get the context object from its parent. (Context object should also be defined in base class. And it may be of generic type.)

edit:

abstract class BaseCommand<T> : ICommand
{
    public T Context { get; set; }
    public BaseCommand Parent { get; set; }
}

class MultiCommand : BaseCommand 
{
     public void AddChildCommand(BaseCommand command) 
     {
         command.parent = this; // we can get parent's context from children now
         // put the command in an internal list
     }
}

var commandsGroup = new MultiCommand();
commandsGroup.AddChildCommand(new Command1());
commandsGroup.AddChildCommand(new Command2());
commandsGroup.AddChildCommand(new Command3());

commandsGroup.Execute()

2) We may create a global singleton context object. In MultiCommand's Execute function, we could set the current context object before executing children's Execute function. Then child command could just access the singleton context object. And after all children's execution, the MultiCommand could reset the context. (The context is actually a stack here.)

edit:

abstract class BaseCommand : ICommand
{
     // it could be put anywhere else as long as it can be accessed in command's Execute
     // it can also be a stack
     public static CommandContext Context {get; set;} 
}

class MutliCommand : BaseCommand 
{
    public void Execute()
    {
        // do something to BaseCommand.Context

        ChildCommand.Execute();

        // do something to BaseCommand.Context
    }
}

class ChildComand: BaseCommand 
{
     void Execute() 
     {
          // do something with BaseCommand.Context
     }
}

Another option is to put the context object as a parameter of the Execute function:

class MultiCommand : BaseCommand 
{
     void Execute(CommandContext context) 
     {
         Children.Execute(context);
     }
}

Upvotes: 2

JWP
JWP

Reputation: 6963

Consider a Functional Style

public class SomeMainClass{
   public void MultiCommandInit()
    {
        MultiCommand.New()
            .Add(new Command1())
            .Add(new Command2())
            .Add(new Command3())
            .SharedContext(CC => {

                CC.Data = new RequiredData();
                CC.ServerController = GetServerController();
            });

    }

    private IServerController GetServerController()
    {
        // return proper instance of server controller
        throw new NotImplementedException();
    }
}

Requires this extension method / function...

  public static class XMultiCommand
    {
        //  How can I have a shared context like this for all Commands of the group?
        public static MultiCommand SharedContext(this MultiCommand mc, Action<CommandContext> CallBack)
        {
            var cc = new CommandContext();            
            CallBack(cc);
            mc.SharedContext = cc;
            return mc;
        }

    }

Finally, these changes to MultiCommand

public class MultiCommand
{
    private System.Collections.Generic.List<ICommand> list;
    public List<ICommand> Commands { get { return list; } }
    public CommandContext SharedContext { get; set; }

    public MultiCommand() { }
    public MultiCommand(System.Collections.Generic.List<ICommand> list)
    {
        this.list = list;
    }
    public MultiCommand Add(ICommand cc)
    {
        list.Add(cc);
        return this;
    }

    internal void Execute()
    {
        throw new NotImplementedException();
    }
    public static MultiCommand New()
    {
        return new MultiCommand();
    }
}

Cool Things Happen Using Functional Styles

  1. Re-usability soars!
  2. Hyper focus on Single Responsibility concerns
  3. Composition becomes the Norm
  4. Code Maintenance becomes simple
  5. Intellisense becomes your built-in API (just use code commenting)
  6. No radical OOP design patterns are needed
  7. Fluent code becomes very enjoyable to work with
  8. Nested / Decorated Functions are much more easy to imagine and implement
  9. You will never repeat youerself
  10. The Open/Closed principal becomes your religion
  11. Code is now always Clear, Complete and Concise
  12. Some even say no interfaces are needed any longer

Upvotes: 3

jlvaquero
jlvaquero

Reputation: 8785

And what about changing your approach? I did an architecture for DDD recently and executing a commad implies atomic operation (retrieve aggregate root from persitence, apply domain rules and pesist the aggregate) so I do not in needed of a share context and can batch multiple commands whithout worries.

Here you have an cqrs architecture that use command pattern with the above strategy I posted.

Upvotes: 2

Valentin Korolev
Valentin Korolev

Reputation: 331

1) If you want to keep this interface, then you have to pass this context as constructor parameter:

new MultiCommand(new List<ICommand>()
            {
                new Command1(context),
                new Command2(context),
                new Command3(context),
            })

2) As another option you can accept list of delegates instead of list of commands. MultiCommand will be look like this:

class MultiCommand : ICommand
{
    public MultiCommand(List<Func<Context, Command>> commands, Context context)

}

That is almost the same except MultiCommand is responsible for all the commands share the same context.

3) Looks like commands in MultiCommand depends on result of previous command. In this case Command pattern is not probably the best. Maybe you should try to implement Middleware chain here?

interface IMiddleware<TContext>
{
   void Run(TContext context);
}

class Chain<TContext>
{
    private List<IMiddleware<TContext>> handlers;

    void Register(IMiddleware<TContext> m);

    public void Run(TContext context)
    {
        handlers.ForEach(h => h.Run(context));
    }
}

Upvotes: 5

wablab
wablab

Reputation: 1743

You could have a constructor on your BaseCommand class (and its derived classes) that would accept a Context class of some kind. When instantiating the commands that will belong to the same group, you could provide them all the same context object. Maybe something like:

public class CommandContext
{
    // The object that will be the target of the commands' actions.
    public object Data { get; set; }

    // ... any other properties that might be useful as shared state between commands...
}

public abstract class BaseCommand : ICommand
{
    protected CommandContext Context { get; private set; }

    public BaseCommand(CommandContext ctx)
    {
        Context = ctx;
    }
}

public class ChangeSomethingIDCommand : BaseCommand
{
    public ChangeSomethingIDCommand(CommandContext ctx) : base(ctx)
    { }

    public void Execute()
    {
        var target = (SomeDomainClass)Context.Data;
        target.SomethingID++;
    }
}

// Elsewhere in your code (assuming 'myTargetDomainClassInstance' is
// a SomeDomainClass instance that has been instantiated elsewhere and
// represents the object upon which the commands will do work):
var ctx = new CommandContext { Data = myTargetDomainClassInstance };
var commandGroup = new MultiItemCommand(ctx, new List<ICommand>
    {
        new ChangeSomethingIDCommand(ctx),
        new Command2(ctx),
        new Command3(ctx)
    });

commandGroup.Execute();

Upvotes: 3

Daniel A. White
Daniel A. White

Reputation: 191037

I would suggest to make somethings generic. Here is a super simple example.

class MultiCommand<TContext>  
{
    List<Command<TContext>> Commands;
    TContext Context;
}

Upvotes: 3

Related Questions