8

データベースから読み取ってスレッドを使用して並行して実行する必要があるデータベースにキューに入れられたジョブのリストがあり、それらの各ジョブを実行するためのコマンド クラスのリストがあり、すべて共通のインターフェイス (コマンド パターン) を実装しています。しかし、データベースから保留中のジョブを取得するときは、ジョブごとに次のような正しいコマンド オブジェクトをインスタンス化する必要があります (ファクトリ クラスで)。

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

command.Execute();

上記のような大きな switch ステートメントを使用せずに、適切なコマンド オブジェクトを作成するより良い方法はありますか? または、キューに入れられたジョブを実行するための他のパタ​​ーンはありますか?

解決策:このように解決しました(選択した回答に基づいて)。これにより、コマンド オブジェクトの遅延インスタンス化が行われます。

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();
4

3 に答える 3

14

ほとんどの C# コマンド パターンの実装は、Java の実装とほぼ同じです。これらの実装では通常、ICommand インターフェイスを使用します。

public interface ICommand
{
    void Execute();
}

そして、すべてのコマンド クラスが強制的にインターフェイスを実装します。私はこのソリューションに問題はありませんが、個人的にはあまり多くのクラスを作成するのは好きではなく、代わりに .NET デリゲートを使用することを好みます (Java にはデリゲートはありません)。Action デリゲートは通常、メソッド参照が 1 つだけ必要な場合にこのトリックを実行します。

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()
    {
    }
}

コマンド インターフェイスに次のような複数のメソッドが必要な場合:

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

次のようなラッパー クラスを使用できます。

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; }
}

または(どちらでも構いません)

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();
    }
}

(これは、既に使用しているレガシーなものがある場合は ICommand を実装することさえできます。インターフェイスを使用する場合、ファクトリは Command クラスの代わりにインターフェイスを使用する必要があります)

このようなラッパーを使用すると、サポートするアクションごとにコマンド クラスを作成する必要がなくなります。次の例は、ラッパー クラスの使用方法を示しています。

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()
    {
    }
}

ご覧のとおり、複数のメソッド (Execute、Undo など) がある場合でも、インターフェイスを実装する必要はありません。Execute メソッドと Undo メソッドは異なるクラスに属している可能性があることに注意してください。より自然に感じられる方法で自由にコードを構成でき、コマンド パターンを使用することもできます。

于 2012-01-09T12:49:56.587 に答える
4

Dictionarya を使用して、文字/文字を関連するICommand実装にマップできます。何かのようなもの:

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();
    }
}

これを使用して、コマンド タイプを登録し、それらを にstring基づくキーにマップして、インスタンス化してより一般的に実行できるようにすることができます。コマンド タイプを最初に使用するときにインスタンス化するだけで、パフォーマンスを向上させることができます。

編集

あなたのコメントに答えて、実行時にのみインスタンス化するには、次のようなことができます:

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();

    // ...
}
于 2012-01-09T12:32:36.527 に答える
1

ジョブに独自の ICommand を提供するように依頼することを検討できます。

interface IJob 
{
  ICommand Command { get; }
}

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

次に、jobCode をオンにする代わりに、次のようにします。

job.Command.Execute();
于 2012-01-09T12:30:00.477 に答える