using the command and factory design patterns for executing queued jobs

泄露秘密 提交于 2019-12-03 20:15:45

问题


I have a list of jobs queued in the database which I need to read from database and execute them in parallel using threading and I have a list of command classes to execute each of those jobs all implementing a common interface (command pattern). but when I retrieve the pending jobs from the database, I will need to instantiate the right command object for each job something like this (in a factory class)

ICommand command;
switch (jobCode)
{
  case "A":
     command = new CommandA();
     break;
  case "B":
     command = new CommandB();
     break;
  case "C":
     command = new CommandC();
     break;
}

command.Execute();

Is there a better way to create the right command object without using a big switch statement like above? OR is there any other pattern for executing the queued jobs?

Solution: solved like this (based on the selected answer). This will do lazy instantiation of the command objects.

public class CommandFactory
{
    private readonly IDictionary<string, Func<ICommand>> _commands;

    public CommandFactory()
    {
        _commands = new Dictionary<string, Func<ICommand>>
                        {
                            {"A", () => new CommandA()},
                            {"B", () => new CommandB()},
                            {"C", () => new CommandC()}
                        };
    }

    public ICommand GetCommand(string jobKey)
    {
        Func<ICommand> command;
        _commands.TryGetValue(jobKey.ToUpper(), out command);
        return command();
    }
}    

Client: 

        var factory = new CommandFactory();
        var command = factory.GetCommand(jobKey);
        command.Execute();

回答1:


Most C# command pattern implementations more or less the same as a Java implementation. These implementations usually use a ICommand interface:

public interface ICommand
{
    void Execute();
}

and then all command classes are forced to implement the interface. I have no issues with this solution, but personally I don't like creating too many classes and I prefer to use .NET delegates instead (there are no delegates in Java). The Action delegate usually does the trick if only need one method reference:

public class Prog
{
    public Prog()
    {
        var factory = new CommandFactory();
        factory.Register("A", () => new A().DoA);            
        factory.Register("B", () => new B().DoB);
        factory.Register("C", DoStuff);

        factory.Execute("A");
    }

  public static void DoStuff()
    {
    }
}

public class CommandFactory
{
    private readonly IDictionary<string, Action> _commands;       

    public void Register(string commandName, Action action)
    {
    _commands.Add(commandName, action); 
    }

    public Action GetCommand(string commandName)
    {
        _commands[commandName];
    }

    public void Execute(string commandName)
    {
        GetCommand(commandName)();
    }
}
public class A
{
    public void DoA()
    {
    }
}

public class B
{
    public void DoB()
    {
    }
}

If your command interface needs more than one methods like:

public interface ICommand
{
    void Execute();
    void Undo();
}

You can use a wrapper class like this:

public class Command
{
    public Command(Action execute, Action undo)
    {
        Execute = execute;
        Undo = undo;
    }

    public Action Execute { get; protected set; }
    public Action Undo { get; protected set; }
}

or (it doesn't matter which one)

public class Command 
{
    private readonly Action _execute;
    private readonly Action _undo;

    public Command(Action execute, Action undo)
    {
        _execute = execute;
        _undo = undo;
    }

    public void Execute()
    {
        _execute();
    }

    public void Undo()
    { 
        _undo();
    }
}

(this one may even implement ICommand if you have legacy stuff using it already. If you use the interface the factory should use the interface instead of the Command class)

With a wrapper like this you are not forced to create a command class for each action you want to support. The following example demonstrates how you can use the wrapper class:

public class Prog2
{
    public Prog2()
    {
        var factory = new CommandFactory2();
        factory.Register("A", new Lazy<Command>(
            ()=>
                {
                    var a = new A();
                    return new Command(a.DoA, a.UndoA);
                }));

        factory.Register("B", new Lazy<Command>(
           () =>
           {
               var c = new B();
               return new Command(c.DoB, c.DoB);
           }));

        factory.Register("C", new Lazy<Command>(
            () => new Command(DoStuff, UndoStuff)));

        factory.Execute("A");
    }

    public static void DoStuff()
    {
    }

    public static void UndoStuff()
    {
    }
}

public class CommandFactory2
{
    private readonly IDictionary<string, Lazy<Command>> _commands;

    public void Register(string commandName, Lazy<Command> lazyCommand)
    {
        _commands.Add(commandName, lazyCommand);
    }

    public void Register(string commandName, Action execute, Action undo)
    {
        _commands.Add(commandName, new Lazy<Command>(() => new Command(execute, undo)));
    }

    public Command GetCommand(string commandName)
    {
        return _commands[commandName].Value;
    }

    public void Execute(string commandName)
    {
        GetCommand(commandName).Execute();
    }

    public void Undo(string commandName)
    {
        GetCommand(commandName).Undo();
    }
}


public class A
{
    public void DoA()
    {
    }

    public void UndoA()
    {
    }
}

public class B
{
    public void DoB()
    {
    }

    public void UndoB()
    {
    }
}

As you can see there is no need to implement the interface even if you have more than one method (Execute, Undo, etc). Please note that the Execute and Undo methods may belong to different classes. You are free to structure your code the way it feels more natural and still can use the command pattern.




回答2:


You could use a Dictionary to map the letter/character to the relevant ICommand implementation. Something like:

public class CommandFactory
{
    private readonly Dictionary<string, ICommand> mCommands = new Dictionary<string,ICommand>(StringComparer.OrdinalIgnoreCase);

    public void RegisterCommand<TCommand>(string commandKey) where TCommand : ICommand, new()
    {
        // Instantiate the command
        ICommand command = new TCommand();

        // Add to the collection
        mCommands.Add(commandKey, command);
    }

    public void ExecuteCommand(string commandKey)
    {
        // See if the command exists
        ICommand command;
        if (!mCommands.TryGetValue(commandKey, out command))
        {
            // TODO: Handle invalid command key
        }

        // Execute the command
        command.Execute();
    }
}

Using this, you can register command types and map them to string-based keys and allow them to be instantiated and executed more generically. You could improve performance by only instantiating the command types when they are first used.

EDIT

In answer to your comment, to instantiate only when executing, you could do something like:

public class CommandDetails<T> where T : ICommand, new()
{
    private ICommand mCommand;

    public ICommand GetCommand()
    {
        if (/* Determine if the command has been instantiated */)
        {
            // Instantiate the command
            mCommand = new T();
        }

        return mCommand;
    }
}

public void ExecuteCommand(...)
{
    // See if the command exists
    CommandDetails details;
    // ...

    // Get the command
    // Note: If we haven't got the command yet, this will instantiate it for us.
    ICommand command = details.GetCommand();

    // ...
}



回答3:


You could consider asking your job to provide it's own ICommand:

interface IJob 
{
  ICommand Command { get; }
}

public class JobA : IJob
{
  private readonly ICommand _command = new CommandA();
  public ICommand Command { get { return _command; } }
}

Then rather than switching on the jobCode, you could just do:

job.Command.Execute();


来源:https://stackoverflow.com/questions/8788366/using-the-command-and-factory-design-patterns-for-executing-queued-jobs

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!