GoFデザインパターン Strategyパターンについて

GoFデザインパターンのStrategyパターンについて勉強したのでメモ。

Strategyパターンとは

Strategyパターンは一連のアルゴリズムを定義し、それぞれをカプセル化してそれらを交換可能にする。Strategyパターンによって、アルゴリズムを使用するクライアントとは独立して、アルゴリズムを変更出来る。

Strategyパターンの適用例

ここでは動物の鴨(Duck)の振る舞いを実装するクラスを例にとってStrategyパターンの適用例を考えてみる。

鴨を実装する時は鴨の共通動作がまとめられたDuckクラスを継承して行う。鴨の種類には普通の鴨とオモチャの鴨が存在する。普通の鴨の場合は「飛ぶ」ことが出来るのでFlyWithWingsクラスを使用し、オモチャの鴨の場合は「飛ぶ」ことが出来ないのでFlyNoWayクラスを使用する。当たり前だが、鴨の「飛ぶ」振る舞いは普通の鴨とオモチャの鴨で異なるので、Duckクラスに持たせることはできない。

以下では、Duck抽象クラスの一部の機能(振る舞い)をカプセル化して、FlyBehaviorインターフェースとFlyWithWingsクラス,FlyNoWayクラスに「飛ぶ」振る舞いとして抽出している。

Duckクラス

public abstract class Duck {

    //インターフェース型の変数を宣言する。サブクラスはこれを継承する。
    FlyBehavior flyBehavior;
    public Duck(){}
    
    //鴨の表示はサブクラスで実装される。
    public abstract void display();
    
    public void performFly(){
        //「飛ぶ」振る舞いは他のクラスに委譲する。
        flyBehavior.fly();
    }
    
    public void swim(){
        System.out.println("全ての鴨は浮かぶ。オモチャの鴨でも。");
    }
}

FlyBehaviorインターフェース

public interface FlyBehavior {
    public void fly();
}

FlyWithWingsクラス

public class FlyWithWings implements FlyBehavior {
    public void fly(){
        System.out.println("飛んでいます。");
    }
}

FlyNoWayクラス

public class FlyNoWay implements FlyBehavior {
    public void fly(){
        System.out.println("飛べません。");
    }
}

上記を組み込んだクラスが以下のMallardDuckクラスとなる。

MallardDuckクラス

public class MallardDuck extends Duck {
    public MallardDuck(){
        //Duckクラスから継承したflyBehavior変数が使用される。
        flyBehavior = new FlyWithWings();
    }
    public void display(){
        System.out.println("本物のマガモです。");
    }
}

上記で書いたコードを実際に使ってテストしてみる。

テストクラス

public class MiniDuckSimulator {
    public static void main(String[] args) {
        Duck mallard = new MallardDuck();
        mallard.performFly();
    }
}

Duckクラスを改良する

MallardDuckクラスの「飛ぶ」振る舞いの型をMallardDuckクラスのコンストラクタでインスタンス化するのではなく、Duckのサブクラスの設定メソッドを使用して設定したい場合を考えてみる。これはGoFデザインパターンの設計原則「インターフェースに対してプログラミングする」に沿ったものとなる。こう設計することで、「飛ぶ」振る舞いを動的に変更可能となる。

Duckクラスに新しいsetFlyBehaviorメソッドを追加する。

public abstract class Duck {
    FlyBehavior flyBehavior;
    public Duck(){ }
    public abstract void display();
    public void performFly(){
        flyBehavior.fly();
    }
    public void swim(){
        System.out.println("全ての鴨は浮かぶ。オモチャの鴨でも。");
    }
    
    //追加
    public void setFlyBehavior(FlyBehavior fb){
        flyBehavior = fb;
    }
}

上記で書いたコードを実際に使ってテストしてみる。

テストクラス

public class MiniDuckSimulator {
    public static void main(String[] args) {
        Duck mallard = new MallardDuck();
        
        //MallardDuckのコンストラクタで指定された通り、鴨は普通に飛ぶ。
        mallard.performFly();
        
        //setterで「飛ぶ」振る舞いを変更。
        mallard.setFlyBehavior(new FlyNoWay());
        
        //飛ぶことができなくなった。
        mallard.performFly();
    }
}

参考
書籍 HeadFirstデザインパターン