スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

オブジェクト指向入門 第7回 ポリモーフィズムによる条件分岐の排除

今回は、ポリモーフィズムのサンプルを書いてみたいと思います。

題材はパックマンです。
実際のゲームは作りませんが、モンスターの個性の違いをポリモーフィズムで表現したいと思います。

さて、パックマンのモンスターは4匹いますが、それぞれ以下のように違う動きをします。

  赤・・・パックマンを直線的に追いかけます。
  ピンク・・・先回りして待ち伏せします。
  青・・・気まぐれに動きます。
  オレンジ・・・追いかける気がないのか、とぼけた動きをします。

この個性の違いを、以下の様に画面表示するプログラムを書きたいと思います。

Pacman.jpg


単純な話、モンスターは種類ごとに一匹なので、次のように書けばOKです。

using System;

namespace Pacman
{
    class Program
    {
        /// <summary>
        /// メイン関数
        /// </summary>
        static void Main(string[] args)
        {
            // 赤モンスター
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine("追いかけます。");

            // ピンクモンスター
            Console.ForegroundColor = ConsoleColor.Magenta;
            Console.WriteLine("待ち伏せます。");

            // 青モンスター
            Console.ForegroundColor = ConsoleColor.Blue;
            Console.WriteLine("気まぐれに動きます。");

            // オレンジモンスター
            Console.ForegroundColor = ConsoleColor.DarkYellow;
            Console.WriteLine("おとぼけに動きます。");

            // 文字色をリセット
            Console.ResetColor();

            // 終了処理
            Console.WriteLine("\n" + "Press any key to exit.");
            Console.ReadKey();
        }
    }
}

これでは芸がないので、4種類のモンスターがそれぞれ何匹ずついても大丈夫なプログラムを書いてみたいと思います。

まずは、ポリモーフィックではない例です。
ここからダウンロードできます。)

using System;

namespace Pacman
{
    class Program
    {
        // モンスターの種類を定義
        enum Monster { Red, Pink, Blue, Orange };

        /// <summary>
        /// メイン関数
        /// </summary>
        static void Main(string[] args)
        {
            // モンスター軍団作成(赤だけ2匹)
            Monster[] monsters = 
            { 
                Monster.Red,
                Monster.Red,
                Monster.Pink,
                Monster.Blue,
                Monster.Orange
            };

            // 一匹ずつ動かす
            foreach (Monster monster in monsters)
            {
                MoveMonster(monster);   // サブルーチン
            }

            // 終了処理
            Console.WriteLine("\n" + "Press any key to exit.");
            Console.ReadKey();
        }

        /// <summary>
        /// モンスターの動きを出力
        /// </summary>
        private static void MoveMonster(Monster monster)
        {
            // 種類によって動きが違う
            switch (monster)
            {
                case Monster.Red:
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.WriteLine("追いかけます。");
                    break;

                case Monster.Pink:
                    Console.ForegroundColor = ConsoleColor.Magenta;
                    Console.WriteLine("待ち伏せます。");
                    break;

                case Monster.Blue:
                    Console.ForegroundColor = ConsoleColor.Blue;
                    Console.WriteLine("気まぐれに動きます。");
                    break;

                case Monster.Orange:
                    Console.ForegroundColor = ConsoleColor.DarkYellow;
                    Console.WriteLine("おとぼけに動きます。");
                    break;
            }

            // 文字色をリセット
            Console.ResetColor();
        }
    }
}


このコードは、モンスターの動きを変えたり、新しいモンスターを追加するとき、case文の中を変更する必要があります。
このプログラムではcase文1つだけですが、実際のプログラムでは同様に分岐しているcase文やif文を漏らさずチェックする必要があります。
1つでもチェック漏れがあった場合、それがバグの原因となる危険性があります。

一方、ポリモーフィックなプログラムでの変更作業は、新しいモンスタークラスを追加して生成するだけです。
case文やif文を1つずつチェックする作業は存在しません。
なぜなら、条件分岐自体が存在しないからです。

上のコードを以下の手順でポリモーフィックにしていきます。

  ①モンスターを多形で表現します。
    ⇒モンスターにも、赤/ピンク/青/オレンジがある
      ⇒モンスタークラスの派生クラスに、赤/ピンク/・・・
  ②MoveMonster関数の引数monsterを「今日の○○」と見立てます。
   (毎回違うモンスターなので)

クラス図とコードは以下の様になります。
(モンスタークラスは生成されないので、interfaceにしています。)

PacmanClass.jpg

using System;

namespace Pacman
{
    class Program
    {
        /// <summary>
        /// モンスターのインターフェイス定義
        /// </summary>
        interface IMonster
        {
            void Move();
        }

        /// <summary>
        /// 赤モンスターの実装
        /// </summary>
        class RedMonster : IMonster
        {
            void IMonster.Move()
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine("追いかけます。");
                Console.ResetColor();
            }
        }

        /// <summary>
        /// ピンクモンスターの実装
        /// </summary>
        class PinkMonster : IMonster
        {
            void IMonster.Move()
            {
                Console.ForegroundColor = ConsoleColor.Magenta;
                Console.WriteLine("待ち伏せます。");
                Console.ResetColor();
            }
        }

        /// <summary>
        /// 青モンスターの実装
        /// </summary>
        class BlueMonster : IMonster
        {
            void IMonster.Move()
            {
                Console.ForegroundColor = ConsoleColor.Blue;
                Console.WriteLine("気まぐれに動きます。");
                Console.ResetColor();
            }
        }

        /// <summary>
        /// オレンジモンスターの実装
        /// </summary>
        class OrangeMonster : IMonster
        {
            void IMonster.Move()
            {
                Console.ForegroundColor = ConsoleColor.DarkYellow;
                Console.WriteLine("おとぼけに動きます。");
                Console.ResetColor();
            }
        }

        /// <summary>
        /// メイン関数
        /// </summary>
        static void Main(string[] args)
        {
            // モンスター軍団作成(赤だけ2匹)
            IMonster[] monsters =
            {
                new RedMonster(),
                new RedMonster(),
                new PinkMonster(),
                new BlueMonster(),
                new OrangeMonster()
            };

            // 一匹ずつ動かす
            foreach (IMonster monster in monsters)
            {
                MoveMonster(monster);   // サブルーチン
            }

            // 終了処理
            Console.WriteLine("\n" + "Press any key to exit.");
            Console.ReadKey();
        }

        /// <summary>
        /// モンスターの動きを出力
        /// </summary>
        private static void MoveMonster(IMonster monster)
        {
            //「今日のモンスター」をただ動かす。
            monster.Move();
        }
    }
}


このコードは変更が簡単です。
例えば、緑モンスターを追加する時は、緑モンスタークラスを作成してモンスター軍団の一員とするだけです。
厄介なcase文やif文も全く存在しません。

ただ、欠点もあります。
各モンスターのMove操作を見ると、どれも同じような処理となっています。
このように重複コードがありますと、Move操作の中身を変更したい時は、全てのモンスタークラスを変更することになってしまいます。
そして、1つでも修正漏れを残してしまうと、それがバグの原因となってしまいます。

インターフェイスを実装する時や、基本クラスの操作をオーバーライドする時の、コードが重複してしまうのは典型的な問題です。
これらは「似て非なる処理」を書きたい時に便利な方法なので、どうしても似た部分が重複コードとなりやすいのです。


今回はインターフェイスと各モンスタークラスの間に抽象クラスを挟み込み、そこに重複コードを移す事にします。
クラス図とコードは以下のようになります。

PacmanClass2.jpg

using System;

namespace Pacman
{
    class Program
    {
        /// <summary>
        /// モンスターのインターフェイス定義
        /// </summary>
        interface IMonster
        {
            void Move();
        }

        /// <summary>
        /// モンスタークラス(抽象クラス)
        /// </summary>
        abstract class Monster : IMonster
        {
            // 移動処理
            void IMonster.Move()
            {
                // 似ている部分(重複コード)を共通化
                Console.ForegroundColor = GetSkinColor();
                Console.WriteLine(GetAlgorithm());
                Console.ResetColor();
            }

            // 非なる部分は抽象操作とし、派生クラスでオーバーライド
            protected abstract ConsoleColor GetSkinColor();
            protected abstract string GetAlgorithm();
        }

        /// <summary>
        /// 赤モンスターの実装
        /// </summary>
        class RedMonster : Monster
        {
            protected override ConsoleColor GetSkinColor()
            {
                return ConsoleColor.Red;
            }

            protected override string GetAlgorithm()
            {
                return "追いかけます。";
            }
        }

        /// <summary>
        /// ピンクモンスターの実装
        /// </summary>
        class PinkMonster : Monster
        {
            protected override ConsoleColor GetSkinColor()
            {
                return ConsoleColor.Magenta;
            }

            protected override string GetAlgorithm()
            {
                return "待ち伏せます。";
            }
        }

        /// <summary>
        /// 青モンスターの実装
        /// </summary>
        class BlueMonster : Monster
        {
            protected override ConsoleColor GetSkinColor()
            {
                return ConsoleColor.Blue;
            }

            protected override string GetAlgorithm()
            {
                return "気まぐれに動きます。";
            }
        }

        /// <summary>
        /// オレンジモンスターの実装
        /// </summary>
        class OrangeMonster : Monster
        {
            protected override ConsoleColor GetSkinColor()
            {
                return ConsoleColor.DarkYellow;
            }

            protected override string GetAlgorithm()
            {
                return "おとぼけに動きます。";
            }
        }

        // ★以下は変更なし!!★

        /// <summary>
        /// メイン関数
        /// </summary>
        static void Main(string[] args)
        {
            // モンスター軍団作成(赤だけ2匹)
            IMonster[] monsters =
            {
                new RedMonster(),
                new RedMonster(),
                new PinkMonster(),
                new BlueMonster(),
                new OrangeMonster()
            };

            // 一匹ずつ動かす
            foreach (IMonster monster in monsters)
            {
                MoveMonster(monster);   // サブルーチン
            }

            // 終了処理
            Console.WriteLine("\n" + "Press any key to exit.");
            Console.ReadKey();
        }

        /// <summary>
        /// モンスターの動きを出力
        /// </summary>
        private static void MoveMonster(IMonster monster)
        {
            //「今日のモンスター」をただ動かす。
            monster.Move();
        }
    }
}


これで重複コードもなくなりました。


■まとめ
  ・ポリモーフィックな処理は、条件分岐を排除できる。
  ・重複コードには注意。


次回は、クラスの追加だけでは対応できない変更をしたい時についてを書きたいと思います。
スポンサーサイト

テーマ : ソフトウェア開発
ジャンル : コンピュータ

プロフィール

きっぽ

Author:きっぽ

カテゴリ
最新記事
最新コメント
最新トラックバック
月別アーカイブ
FC2カウンター
検索フォーム
RSSリンクの表示
リンク
ブロとも申請フォーム

この人とブロともになる

QRコード
QRコード
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。