Winformアプリで シンプルなコードからStrategyパターンへ移行しよう

このチュートリアルでは、簡単な攻撃プログラムを作成し、それをより柔軟で拡張しやすい設計に改善する方法を学びます。まず、シンプルなコードを書き、その後、Strategyパターンというデザインパターンを導入して、コードの設計を改善していきます。

ステップ1: シンプルなコードを作成する

まずは、以下のシンプルなコードを見てください。このコードは、コンボボックスから選択された攻撃タイプに応じて、メッセージを表示するものです。

public partial class Form1 : Form
{
    private string selectedAttack;

    public Form1()
    {
        InitializeComponent();

        comboBox1.Items.Add("剣");
        comboBox1.Items.Add("魔法");
        comboBox1.Items.Add("弓");

        selectedAttack = "剣";  // 初期選択
    }

    private void button1_Click(object sender, EventArgs e)
    {
        switch (comboBox1.SelectedItem.ToString())
        {
            case "剣":
                selectedAttack = "剣で攻撃した!";
                break;
            case "魔法":
                selectedAttack = "魔法を使った!";
                break;
            case "弓":
                selectedAttack = "弓で攻撃した!";
                break;
        }

        MessageBox.Show(selectedAttack);
    }
}

このコードは、シンプルで理解しやすいですが、攻撃タイプが増えるたびにswitch文を追加する必要があり、コードが複雑になりがちです。

ステップ2: 問題点を理解する

この設計には、以下のような問題があります:

  1. コードの一貫性の欠如switch文を使用して攻撃タイプごとに異なる処理を追加しているため、メソッドが複数の責任を持ち、肥大化しがちです。これにより、コードが一貫性を欠き、他の開発者が理解しにくくなります。
  2. 保守性の低下: 新しい攻撃タイプを追加するたびに、Form1クラス内でswitch文に新しいケースを追加しなければなりません。これにより、Form1クラスが攻撃の実装に依存し、保守が難しくなります。また、コードの変更が複数箇所に広がるため、ミスが発生しやすくなります。
  3. 拡張性の問題: 他の部分でも同様のロジックが必要になった場合、switch文の重複が発生する可能性が高く、拡張が困難です。

Strategyパターンの利点

Strategyパターンを導入することで、switch文を保持しながらも以下のような改善が見込めます:

  • クラスの責任を分離: 各攻撃方法を独立したクラスとして分離することで、Form1クラスが「どの攻撃方法を使うか」だけを担当し、「攻撃がどのように行われるか」は個別のクラスに委譲できます。これにより、クラスの責任が明確になります。
  • コードの集中化: 新しい攻撃方法を追加する際に、Form1クラスではなく、専用のクラスを作成するだけで済みます。これにより、コードの変更箇所が集中し、保守が容易になります。
  • 再利用性の向上: 攻撃ロジックが個別のクラスに集約されているため、他のコンテキストでも再利用可能です。これにより、同じロジックを繰り返し書く必要がなくなります。

このように、Strategyパターンを使うことで、コードの冗長性自体を直接的に減らすわけではありませんが、クラスの責任を明確にし、保守性や拡張性を大幅に向上させることができます。

ステップ3: Strategyパターンの基本を学ぶ

Strategyパターンとは?

Strategyパターンは、アルゴリズムや動作をカプセル化し、それらをインターフェースとして定義します。これにより、動作を簡単に切り替えることができ、コードの柔軟性が向上します。

Strategyパターンの構成

  1. インターフェース: 共通の動作を定義するIAttackインターフェースを作成します。
  2. 具体的なクラスIAttackインターフェースを実装する具体的な攻撃クラス(SwordAttackMagicAttackBowAttack)を作成します。
  3. コンテキストクラス: 攻撃方法を保持し、それを利用するクラス(Character)を作成します。

ステップ4: Strategyパターンを適用する

それでは、シンプルなコードをStrategyパターンを使って改善してみましょう。

1. IAttackインターフェースを作成する

まず、攻撃方法を定義するインターフェースを作成します。

public interface IAttack
{
    string Attack();
}

2. 具体的な攻撃クラスを別ファイルで作成する

次に、IAttackインターフェースを実装する具体的な攻撃クラスを作成します。各クラスは別のファイルに分けて作成してください。

SwordAttack.cs

public class SwordAttack : IAttack
{
    public string Attack()
    {
        return "剣で攻撃した!";
    }
}

MagicAttack.cs

public class MagicAttack : IAttack
{
    public string Attack()
    {
        return "魔法を使った!";
    }
}

BowAttack.cs

public class BowAttack : IAttack
{
    public string Attack()
    {
        return "弓で攻撃した!";
    }
}

クラスを別ファイルで管理することで、コードが整理され、理解しやすくなります。

3. Characterクラスを作成する

キャラクターがどの攻撃方法を使用するかを管理するCharacterクラスを作成します。

public class Character
{
    public string Name { get; set; }
    public IAttack AttackType { get; set; }

    public Character(string name, IAttack attackType)
    {
        Name = name;
        AttackType = attackType;
    }

    public string PerformAttack()
    {
        return $"{Name}は{AttackType.Attack()}";
    }
}

4. Form1クラスを更新する

最後に、Form1クラスをStrategyパターンに合わせて更新します。

public partial class Form1 : Form
{
    private Character character;

    public Form1()
    {
        InitializeComponent();

        comboBox1.Items.Add("剣");
        comboBox1.Items.Add("魔法");
        comboBox1.Items.Add("弓");

        character = new Character("勇者", new SwordAttack());
    }

    private void button1_Click(object sender, EventArgs e)
    {
        switch (comboBox1.SelectedItem.ToString())
        {
            case "剣":
                character.AttackType = new SwordAttack();
                break;
            case "魔法":
                character.AttackType = new MagicAttack();
                break;
            case "弓":
                character.AttackType = new BowAttack();
                break;
        }

        string result = character.PerformAttack();
        MessageBox.Show(result);
    }
}

ステップ5: 振り返りと応用

Strategyパターンを導入したことで、攻撃方法が増えた場合でも、簡単に新しいクラスを作成するだけで対応できるようになりました。また、Characterクラスのコードが非常にシンプルになり、他の動作にも応用が利く設計になりました。

振り返り

  • 柔軟な設計: 新しい攻撃方法を追加する際に、既存のコードをほとんど変更せずに済むため、柔軟な設計が可能です。
  • コードの分離: 各攻撃方法が独立したクラスに分かれているため、コードの見通しが良く、保守性が向上しました。

応用例

このパターンは、攻撃方法に限らず、他の選択肢や動作を切り替えるシーンでも使えます。例えば、キャラクターの移動方法や攻撃対象の選択などにも応用できます。


このチュートリアルを通じて、シンプルな設計からデザインパターンを導入するプロセスを学び、より柔軟で拡張性のあるコードを書けるようになりましょう。