【Java】Strategy(ストラテジ)パターン

徒然草2.0

Strategyパターンは、オブジェクト指向設計において、おそらく最も重要なデザインパターンの1つで、アルゴリズムを差し替えることを可能にして、テストをしやすくすることができます。

解説

Strategy(戦略)パターンとは、あるクラスが持つ処理(Strategy、振る舞い、アルゴリズムと表現する)を、後から簡単に変更・追加できるようにするための設計手法です。

具体的には、「切り替え可能な処理のパターン」をインターフェースとして定義し、それを実装する複数のクラスを用意します。

メインの処理の変更や肥大化を防ぐことができますし、またStrategyから見て上位クラスの動作を知る必要もありません。

実行時には必要な戦略(Strategy)を選択して使うことで、条件分岐を減らし、コードの柔軟性と拡張性を高めることができます。

例えばオブジェクト指向プログラミングにおける以下のような変更や追加が起こりやすいコードの実装に用います。

例1)支払い方法の種別をStrategyとする。
例2)動物クラスの鳴き声をStrategyとする。
例3)RPGキャラの役職をStrategyとする。

以下にJavaのコード例を示します。

(1)支払い方法(Javaのコード例)

// Strategyインターフェース
public interface PaymentStrategy {
    void pay(int amount);
}

// ConcreteStrategyA
public class CreditCardPayment implements PaymentStrategy {
    public void pay(int amount) {
        System.out.println("クレジットカードで " + amount + " 円支払いました。");
    }
}

// ConcreteStrategyB
public class PayPayPayment implements PaymentStrategy {
    public void pay(int amount) {
        System.out.println("PayPayで " + amount + " 円支払いました。");
    }
}

// Context
public class ShoppingCart {
    private PaymentStrategy paymentStrategy;

    public ShoppingCart(PaymentStrategy strategy) {
        this.paymentStrategy = strategy;
    }

    public void checkout(int amount) {
        paymentStrategy.pay(amount);
    }
}

// 使用例
public class Main {
    public static void main(String[] args) {
        ShoppingCart cart1 = new ShoppingCart(new CreditCardPayment());
        cart1.checkout(5000);  // → クレジットカードで 5000 円支払いました。

        ShoppingCart cart2 = new ShoppingCart(new PayPayPayment());
        cart2.checkout(3000);  // → PayPayで 3000 円支払いました。
    }
}

(2)動物クラス(Javaのコード例)

interface BarkStrategy {
    void bark();
}

class DogBark implements BarkStrategy {
    public void bark() { System.out.println("ワンワン!"); }
}

class CatBark implements BarkStrategy {
    public void bark() { System.out.println("ニャー!"); }
}

class Animal {
    private BarkStrategy barkStrategy;

    public Animal(BarkStrategy strategy) {
        this.barkStrategy = strategy;
    }

    public void makeSound() {
        barkStrategy.bark();
    }

    public void setBarkStrategy(BarkStrategy strategy) {
        this.barkStrategy = strategy;
    }
}

(3)RPGキャラの役職(Javaのコード例)

interface JobStrategy {
    void useSkill();
}

class WarriorJob implements JobStrategy {
    public void useSkill() { System.out.println("剣を振る!"); }
}

class MageJob implements JobStrategy {
    public void useSkill() { System.out.println("ファイアーボール!"); }
}

class Hero {
    private JobStrategy job;

    public Hero(JobStrategy job) {
        this.job = job;
    }

    public void setJob(JobStrategy job) {
        this.job = job;
    }

    public void useSkill() {
        job.useSkill();
    }
}

補足:Strategyパターンと依存性注入(DI)の違い

結論から言うと、Strategyパターンを使用した場合は依存性注入という考えをといれるのが一般的である。StrategyパターンとDIはセットで使用する。(Strategyパターンを使っているのにDIしないなら、Strategyパターンを使う意味が無くなりはしないがメリットが減る。

支払い方法の使用例にある、「new ShoppingCart(new CreditCardPayment())」が依存性注入である。依存性を外部から入れることで各クラスを疎結合の関係にして分離することでモックをつくってテストしやすくする意図がある。

RPGの役職の使用例(DIあり)

// --- 使用側 ---
public class Main {
    public static void main(String[] args) {
        JobStrategy warrior = new WarriorJob(); // 外部でインスタンス化
        Hero hero = new Hero(warrior);          // Heroに注入
        hero.useSkill();                         // => 剣を振る!

        // 後で差し替えもできる
        hero.setJob(new MageJob());
        hero.useSkill();                         // => ファイアーボール!
    }
}

⋯以下のコードのように、まちがってHEROクラスの中で役職を呼び出すとテストしづらく柔軟に拡張がしづらくなる。また、以下のようにHeroにクラスを組み込むと修正があるたびにHeroクラスのif文に手をいれることになる↓

RPGの役職の使用例(DIなし)

class Hero {
    private JobStrategy job;

    public Hero(String jobType) {
        // 自分でnewしているので強く依存している
        if ("warrior".equals(jobType)) {
            this.job = new WarriorJob();
        } else if ("mage".equals(jobType)) {
            this.job = new MageJob();
        } else {
            throw new IllegalArgumentException("Unknown job type");
        }
    }

    public void useSkill() {
        job.useSkill();
    }
}

// --- 使用側 ---
public class Main {
    public static void main(String[] args) {
        Hero hero = new Hero("warrior");
        hero.useSkill(); // => 剣を振る!

        // Mageに変更したい? Hero自体を再作成しなければならない
        hero = new Hero("mage");
        hero.useSkill(); // => ファイアーボール!
    }
}
徒然草2.0
スポンサーリンク
シェアする
gomiryoをフォローする
ごみぶろぐ

コメント

タイトルとURLをコピーしました