Search code examples
c#genericsinterfacecommand-pattern

Implementing the command pattern using generics


I'm trying to implement the command pattern, so it works like this:

  • There is a base class Command<T>, from which all other commands inherit.
  • A command can be executed on a class deriving from an Actor.
  • Each interface defines an action an Actor can perform. E.g. IMovable or ITurnable.
  • Each command class acts on a specific interface. E.g. Command<IMovable>.
  • Actors implement interfaces, which define what actions they can perform.

Here is how I tried implementing it:

// base interface for all action interfaces
public interface ICommandable { }

// interface for a move action
public interface IMovable : ICommandable
{
    void Move();
}

// base actor class
public class Actor : ICommandable
{
    public void ExecuteCommand(ICommand<ICommandable> command)
    {
        (command as Command<ICommandable>).Execute(this);
    }
}

// an actor which can be moved
public class MovableActor: Actor, IMovable
{
    public void Move()
    {
        Console.WriteLine("Move");
    }
}

// an interface for commands
public interface ICommand<out T> { }

// base command class
public class Command<T> : ICommand<T> where T : ICommandable
{
    public virtual void Execute(T robot) { }
}

// move command
public class MoveCommand : Command<IMovable>
{
    public override void Execute(IMovable actor)
    {
        actor.Move();
    }
}

This is an example of what I'm trying to do:

MovableActor actor = new MovableActor();
Command<IMovable> cmd = new MoveCommand();
actor.ExecuteCommand(cmd);

The issue with my implementation is that the ICommand interface has to use the out keyword for its parameter. I did some reading and I understand why that is, but I don't know how to achieve what I described. How can I implement this?

If this is not possible, how should I change my description, so it's as close to this one as possible, but works?


Solution

  • The basic problem here is that your MoveCommand.Execute must be given an IMovable -- that method depends on having an IMovable. However, you're allowed to pass anything to Actor.ExecuteCommand, even a command such as an ICommand<ITurntable>: there's nothing stopping you doing this.

    But if you did that, and called MoveCommand.ExecuteCommand with an ICommand<ITurntable>, MoveCommand.Execute would fail, because it wants an IMovable.

    Your basic options are:

    1. Either you don't let actors invoke commands on themselves: invoke the command directly on the actor, or have a separate CommandInvoker take both the action and the command
    2. Prepare for the possibility that someone might send a command to an actor which can't accept that, and check for that and deal with it at runtime. Then it's just Actor.ExecuteCommand(ICommand command), and you do a runtime check to see whether it's the right sort of command.

    That would look something like:

    // base interface for all action interfaces
    public interface ICommandable { }
    
    // interface for a move action
    public interface IMovable : ICommandable
    {
        void Move();
    }
    
    // base actor class
    public abstract class Actor : ICommandable
    {
        public void ExecuteCommand(ICommand command)
        {
            command.Execute(this);
        }
    }
    
    // an actor which can be moved
    public class MovableActor: Actor, IMovable
    {
        public void Move()
        {
            Console.WriteLine("Move");
        }
    }
    
    // an interface for commands
    public interface ICommand
    {
        void Execute(ICommandable robot);
    }
    
    // base command class
    public abstract class Command<T> : ICommand where T : ICommandable
    {
        void ICommand.Execute(ICommandable robot)
        {
            if (robot is T typedRobot)
            {
                Execute(typedRobot);
            }
            else
            {
                // Handle this error
            }
        }
        
        protected abstract void Execute(T robot);
    }
    
    // move command
    public class MoveCommand : Command<IMovable>
    {
        protected override void Execute(IMovable actor)
        {
            actor.Move();
        }
    }