web-dev-qa-db-ja.com

さまざまな入力パラメーターを持つワーカーのC#デザインパターン

どのデザインパターンがこの問題の解決に役立つかわかりません。

私は 'Coordinator'というクラスを持っています。これは、使用するWorkerクラスを決定するもので、そこにあるさまざまなタイプのWorkerをすべて知る必要はありません。WorkerFactoryを呼び出して、共通のIWorkerインターフェイスに作用するだけです。

次に、適切なワーカーを機能するように設定し、その 'DoWork'メソッドの結果を返します。

これは問題ありません...今までは。新しいWorkerクラス "WorkerB"の新しい要件があり、作業を行うために追加の情報量、つまり追加の入力パラメーターが必要です。

追加の入力パラメーターを持つオーバーロードされたDoWorkメソッドが必要なようですが、既存のすべてのワーカーはそのメソッドを実装する必要があります。これらのワーカーは実際にはそのメソッドを必要としないため、これは間違っているようです。

どのようにこれをリファクタリングして、どのワーカーが使用されているかをコーディネーターに知らせず、各ワーカーが仕事に必要な情報を取得できるようにしながら、ワーカーに不要なことをさせないようにできますか?

すでに多くの既存のワーカーがいます。

新しいWorkerBクラスの要件に対応するために、既存の具象ワーカーを変更する必要はありません。

私はおそらくデコレータパターンが良いだろうと思いましたが、同じメソッドで異なるパラメータでオブジェクトを装飾するデコレータを見たことはありません...

コードの状況:

public class Coordinator
{
    public string GetWorkerResult(string workerName, int a, List<int> b, string c)
    {
        var workerFactor = new WorkerFactory();
        var worker = workerFactor.GetWorker(workerName);

        if(worker!=null)
            return worker.DoWork(a, b);
        else
            return string.Empty;
    }
}

public class WorkerFactory
{
    public IWorker GetWorker(string workerName)
    {
        switch (workerName)
        {
            case "WorkerA":
                return new ConcreteWorkerA();
            case "WorkerB":
                return new ConcreteWorkerB();
            default:
                return null;
        }
    }
}

public interface IWorker
{
    string DoWork(int a, List<int> b);
}

public class ConcreteWorkerA : IWorker
{
    public string DoWork(int a, List<int> b)
    {
        // does the required work
        return "some A worker result";
    }
}

public class ConcreteWorkerB : IWorker
{
    public string DoWork(int a, List<int> b, string c)
    {
        // does some different work based on the value of 'c'
        return "some B worker result";
    }

    public string DoWork(int a, List<int> b)
    {
        // this method isn't really relevant to WorkerB as it is missing variable 'c'
        return "some B worker result";
    }    
}
14
JTech

@Dunkのコメントに基づいて、ソリューションを再設計しました。

... IWorkerインスタンスが作成される前に必要なすべてのパラメーターをすでに知っています。したがって、これらの引数をDoWorkメソッドではなくコンストラクタに渡す必要があります。 IOW、ファクトリクラスを利用してください。インスタンス構築の詳細を隠すことが、ファクトリクラスが存在する主な理由のほとんどです。

そのため、IWorkerを作成するために必要なすべての引数をIWorerFactory.GetWorkerメソッドにシフトし、各ワーカーはすでに必要なものを持っているので、コーディネーターはworker.DoWork();を呼び出すだけです。

    public interface IWorkerFactory
    {
        IWorker GetWorker(string workerName, int a, List<int> b, string c);
    }

    public class WorkerFactory : IWorkerFactory
    {
        public IWorker GetWorker(string workerName, int a, List<int> b, string c)
        {
            switch (workerName)
            {
                case "WorkerA":
                    return new ConcreteWorkerA(a, b);
                case "WorkerB":
                    return new ConcreteWorkerB(a, b, c);
                default:
                    return null;
            }
        }
    }

    public class Coordinator
    {
        private readonly IWorkerFactory _workerFactory;

        public Coordinator(IWorkerFactory workerFactory)
        {
            _workerFactory = workerFactory;
        }

        // Adding 'c' breaks Open/Closed principal for the Coordinator and WorkerFactory; but this has to happen somewhere...
        public string GetWorkerResult(string workerName, int a, List<int> b, string c)
        {
            var worker = _workerFactory.GetWorker(workerName, a, b, c);

            if (worker != null)
                return worker.DoWork();
            else
                return string.Empty;
        }
    }

    public interface IWorker
    {
        string DoWork();
    }

    public class ConcreteWorkerA : IWorker
    {
        private readonly int _a;
        private readonly List<int> _b;

        public ConcreteWorkerA(int a, List<int> b)
        {
            _a = a;
            _b = b;
        }

        public string DoWork()
        {
            // does the required work based on 'a' and 'b'
            return "some A worker result";
        }
    }

    public class ConcreteWorkerB : IWorker
    {
        private readonly int _a;
        private readonly List<int> _b;
        private readonly string _c;

        public ConcreteWorkerB(int a, List<int> b, string c)
        {
            _a = a;
            _b = b;
            _c = c;
        }

        public string DoWork()
        {
            // does some different work based on the value of 'a', 'b' and 'c'
            return "some B worker result";
        }
    }
2
JTech

引数を一般化して、基本インターフェイスと可変数のフィールドまたはプロパティを持つ単一のパラメーターに適合するようにする必要があります。このような並べ替え:

public interface IArgs
{
    //Can be empty
}

public interface IWorker
{
    string DoWork(IArgs args);
}

public class ConcreteArgsA : IArgs
{
    public int a;
    public List<int> b;
}

public class ConcreteArgsB : IArgs
{
    public int a;
    public List<int> b;
    public string c;
}

public class ConcreteWorkerA : IWorker
{
    public string DoWork(IArgs args)
    {
        var ConcreteArgs = args as ConcreteArgsA;
        if (args == null) throw new ArgumentException();
        return "some A worker result";
    }
}

public class ConcreteWorkerB : IWorker
{
    public string DoWork(IArgs args)
    {
        var ConcreteArgs = args as ConcreteArgsB;
        if (args == null) throw new ArgumentException();
        return "some B worker result";
    }
} 

Nullチェックに注意してください。システムは柔軟で遅延バインディングであるため、タイプセーフでもないため、キャストをチェックして、渡された引数が有効であることを確認する必要があります。

引数の可能なすべての組み合わせに対して具体的なオブジェクトを作成したくない場合は、代わりに Tuple を使用できます(最初の選択肢にはなりません)。

public string GetWorkerResult(string workerName, object args)
{
    var workerFactor = new WorkerFactory();
    var worker = workerFactor.GetWorker(workerName);

    if(worker!=null)
        return worker.DoWork(args);
    else
        return string.Empty;
}

//Sample call
var args = new Tuple<int, List<int>, string>(1234, 
                                             new List<int>(){1,2}, 
                                             "A string");    
GetWorkerResult("MyWorkerName", args);
9
John Wu

私はいくつかのことを提案します。

カプセル化を維持して、コールサイトがワーカーまたはワーカーファクトリの内部動作について何も知る必要がないようにする場合は、追加のパラメーターを持つようにインターフェイスを変更する必要があります。パラメータにはデフォルト値を設定できるため、一部のコールサイトは2つのパラメータしか使用できません。これには、使用しているライブラリを再コンパイルする必要があります。

もう1つのオプションは、カプセル化を壊し、一般的にOOPが悪いためです。これには、少なくともConcreteWorkerBのすべての呼び出しサイトを変更できることも必要です。 IWorkerインターフェースを実装するクラスを作成できますが、追加のパラメーターを持つDoWorkメソッドもあります。次に、コールサイトでIWorkervar workerB = myIWorker as ConcreteWorkerB;でキャストしてから、具象型で3つのパラメーターDoWorkを使用します。繰り返しますが、これは悪い考えですが、あなたがcouldすることです。

1
JamesFaix

@Jtech、params引数の使用を検討しましたか?これにより、可変量のパラメーターを渡すことができます。

https://msdn.Microsoft.com/en-us/library/w5zay9db(v = vs.71).aspx

0
Jon Raynor