« カリー化が分かるようで、分からない | トップページ | [C#]デザインパターン再考 Strategy Pattern(デリゲートタイプ) »

[C#]デザインパターン再考 Chain of Responsibility Pattern

※ ここに書かれていることは、間違っているかもしれないので、ご注意ください

■ なぜ、このパターンを使うのか?

 switchやif - elseで条件分岐を記述すると・・・

1.読みづらい

  • 処理のコードが長すぎると、読みづらい
  • 条件の数が多すぎると、読みづらい
  • どこまでが1つの処理なのか、分かりづらい

2.他の人(あるいは、未来の自分)が条件を追加、削除しづらい

  • 変更による影響範囲を特定しづらい
  • 試しでコメントアウトするのに、/* ... */や、#if false ... #endif を使わないといけない

3.複雑な判定がやりづらい

  • true / false を判断するのに複雑な処理が必要な場合、どこに書けばいいのか、分からない
  • 処理の途中で行き詰ったときに断念することが難しい

 やはり、読みづらいと変更しづらいというのが、主な理由。

 そもそもオブジェクト指向は、関係する処理をクラスにまとめることで、変更による影響範囲を限定的にするもののはず。複数の条件がかかわるコードを同じクラス内に書くこと自体がおかしい話なのだと思う。

■ 何をしているのか?

 このパターンで行っているのは、

  • 条件ごとの判定と適用する処理を、ひとまとまりのコードとして扱う
  • 呼び出し元は各条件を順次呼び出す
  • 処理ができる条件があれば、呼び出し元は以降の条件は無視する

 つまり、

  • 各条件は、どこから呼び出されているか知らない。ただ、必要な情報をもらって、処理ができるなら処理をするし、できないならできない旨を伝えるだけ
  • 呼び出し元は、各条件が行っている判定や処理の詳細を知らない。ただ、処理ができる条件があれば適切な出力を得られると知っているのみ

 たとえるなら、金属加工の外注だろうか。

  • A社は、自社製品に使用する金属ケースを、非常に薄く、ひずみを小さく、更に表面をピカピカにしたいと思っていた
  • いくつかの金属加工会社に問い合わせたが、要求する品質レベルが高すぎてどこも対応できない
  • 6ヵ月後、16社目でようやく引き受けてくれるところが見つかった。が、実際やってみると非常に難しい加工だったので、ここもすぐに断念した
  • さらに3ヵ月後、23社目で再び引き受けてくれた。ここは加工に成功。十分な品質レベルを確保できることが分かり、採用することになった。

 金属加工にもさまざまはやり方があるだろう。新しい加工方法も開発されていく。それを導入した加工会社が見つかれば、A社の担当が持っている外注先のリストに書き加えておけばいい。次回、同じようなことが起これば、そこにも相談できる。

 A社の担当も金属加工の素人ではないだろうが、すべての加工に精通しているわけでもない。詳細なところの判断は、各加工会社の専門家に聞くのがいい。また、加工の詳細を知らなくても、出来上がった製品が要求している品質を満たしていればいい。

 加工会社からすると、A社の製品の全貌を知る必要はない。秘情報を教えてくれるわけはないし、知ったところで加工にはほぼ関係ない。むしろ、必要な要求をきちんと整理して、そこだけを伝えてもらった方が無駄な作業時間がかからないのでいい。

■ デメリットは?

 条件の数が少なく、判定も単純で、処理の複雑ではない場合、このパターンを使うのはよくない。逆に見通しが悪くなるだけで、読みづらくなる。読みづらいと、変更しづらいコードになる。条件分岐のすべてにこのパターンを適用してはいけない。

■ コード例

 デメリットに書いたとおり、簡単な処理ではこのパターンを使うべきではない。だが、コード例はそれほど複雑にはできない。で、仕方がないので、簡単な処理での例を書く。

    class Program
    {
        static void Main(string[] args)
        {
            var question_result = DialogResult.Ignore;
            var analyzers = new IAnalyzer<string, DialogResult>[]{
                new ConsoleOutAnalyzer(),
                new MessageBoxAnalyzer(),
                new QuestionAnalyzer((result) => question_result = result)
            };

            IEnumerable<string> code = new string[] {
                "print How are you?",
                "message What do you think I say?",
                "print I'm fine, thank you!",
                "ask Are you OK?"
            };

            while (true)
            {
                question_result = DialogResult.Ignore;
                var analyzer = analyzers.FirstOrDefault(d => d.CanAnalyze(code));
                if (analyzer == null) break;
                analyzer.Analyze(ref code);
                if (question_result == DialogResult.Yes)
                    Console.WriteLine("Wow");
            }

            Console.ReadKey();
        }
    }

    interface IAnalyzer<TArg, TResult>
    {
        bool CanAnalyze(IEnumerable<TArg> code);
        void Analyze(ref IEnumerable<TArg> code);
    }

    abstract class ACodeAnalyzer : IAnalyzer<string, DialogResult>
    {
        protected string command = null;

        protected Action<DialogResult> completed = null;

        public virtual bool CanAnalyze(IEnumerable<string> code)
        {
            return command != null
                && code != null
                && code.Count() > 0
                && code.ElementAt(0).StartsWith(command);
        }

        public abstract void Analyze(ref IEnumerable<string> code);
    }

    class ConsoleOutAnalyzer : ACodeAnalyzer
    {
        public ConsoleOutAnalyzer() { command = "print"; }

        public ConsoleOutAnalyzer(Action<DialogResult> completed)
            : this()
        {
            this.completed = completed;
        }

        public override void Analyze(ref IEnumerable<string> code)
        {
            var line = code.ElementAt(0).Replace(command, "").Trim();
            code = code.Skip(1);

            Console.WriteLine(line);
        }
    }

    class MessageBoxAnalyzer : ACodeAnalyzer
    {
        public MessageBoxAnalyzer() { command = "message"; }

        public override void Analyze(ref IEnumerable<string> code)
        {
            var line = code.ElementAt(0).Replace(command, "").Trim();
            code = code.Skip(1);

            Console.WriteLine(line);
            MessageBox.Show(line);
        }
    }

    class QuestionAnalyzer : ACodeAnalyzer
    {
        public QuestionAnalyzer() { command = "ask"; }

        public QuestionAnalyzer(Action<DialogResult> completed)
            : this()
        {
            this.completed = completed;
        }

        public override void Analyze(ref IEnumerable<string> code)
        {
            var line = code.ElementAt(0).Replace(command, "").Trim();
            code = code.Skip(1);

            Console.WriteLine(line);
            var result = MessageBox.Show(line, "Question", MessageBoxButtons.YesNo);
            Console.WriteLine(result == DialogResult.Yes ? "Your answer is YES." : "Your answer is NO.");

            if (this.completed != null)
                this.completed(result);
        }
    }

以上

« カリー化が分かるようで、分からない | トップページ | [C#]デザインパターン再考 Strategy Pattern(デリゲートタイプ) »

C# メモ書き」カテゴリの記事

コメント

コメントを書く

(ウェブ上には掲載しません)

トラックバック


この記事へのトラックバック一覧です: [C#]デザインパターン再考 Chain of Responsibility Pattern:

« カリー化が分かるようで、分からない | トップページ | [C#]デザインパターン再考 Strategy Pattern(デリゲートタイプ) »

2019年10月
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31    
無料ブログはココログ