« 2014年12月 | トップページ | 2015年2月 »

[C#]デザインパターン再考 Strategy Pattern(デリゲートタイプ)

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

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

 switchやif - elseで処理の切り替え(アルゴリズムの変更)を記述すると・・・

1.どの処理を適用するかを示す引数や変数が必要になる

  • ほとんどの場合、名前の付け方がいい加減で、コードが読み難い
  • 数が多くなると、さらに読みづらくなる

2.毎回、上記の引数や変数の判定が必要になる

  • 同じ引数や変数なのに、同じ結果を得るための判定処理を行うのはムダだ
  • ひとつひとつの処理時間は短いが、回数が増えると無視できない時がある

 私の少ない経験から思うに、繰り返し処理されるコードで毎回判定を行っていると、処理時間がもったいないことがある。特に、画像表示を行う場合は。

■ 何をしているのか?

 簡単に言うと、処理を選択する箇所と、それを実行する箇所を分けているのだ。

 switchやif - elseで処理を切り替えるコードでは、条件式で処理の選択を記述し、それに続いて実行する箇所を記述している。したがって、処理を適用したい場合は毎回、判定を行うことになる。何度も同じ判定を繰り返すのはもったいない。同じ処理を行う状態なら、判定は状態の遷移時に行えばいいだけだ。

 そう、これは状態遷移で使うパターンだ。(といって、いいと思うのだけれど)

 画像表示のツールを作った際には、表示の更新/停止の切り替えに使った。カメラから画像データを取得し、処理して、表示するという流れを繰り返すのだが、この流れを停止したい場合もある。その2つの状態の切り替えに使用した。

■ デメリットは?

 引数で状態を指定されて、それに応じて行う処理が簡単であって、処理時間が少々かかっても問題ない場合は、このパターンを適用しなくていいと思う。適用した方が煩雑になるからだ。

 処理時間の短さが重要視される場合や、処理のコードが長くなる場合、このパターンを適用してもいいと思う。

 状態の指定をフィールドで行う(フラグってヤツ)場合も、このパターンを使っていいと思う。フィールドを記述するのと同じ手間で書けるからだ。

■ コード例

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace StrategyPatternByDelegate
{
    class Program
    {
        static void Main(string[] args)
        {
            var program = new Program();
            program.StartImageView();
            program.StartUserInput();
        }

        private static Action ShowImage = () => Console.WriteLine("Show a new image.");

        private static Action StopImage = () => Console.WriteLine("Now stopping ...");

        private Action NowAction = StopImage;

        private Thread thread = null;

        private void StartImageView()
        {
            this.thread = new Thread(() =>
            {
                foreach (var i in Enumerable.Range(0, 100))
                {
                    this.NowAction();
                    Thread.Sleep(100);
                }
            });
            thread.Start();
        }

        private void SelectAction(ConsoleKeyInfo keyInfo)
        {
            if (keyInfo.Key == ConsoleKey.Enter)
                this.NowAction = (this.NowAction == ShowImage) ? StopImage : ShowImage;
        }

        private void StartUserInput()
        {
            if (this.thread == null) return;

            while (thread.IsAlive)
                this.SelectAction(Console.ReadKey());
        }
    }
}

以上

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

以上

« 2014年12月 | トップページ | 2015年2月 »

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    
無料ブログはココログ