振る舞い系デザインパターンにおいて、ステートパターンとストラテジーパターンは実の兄弟のようなもので、非常に似ています。まず両者の一般的なクラス図を見て、並べて比較してみましょう。図の通りです。

両者はとても似ていますよね。このUMLを見ただけでは、何も違いは見えてきません。次に、例を交えて両者の違いを比較してみましょう。以下の例は『Head First デザインパターン』の例です。


策略模式

ストラテジーパターンはアルゴリズム群を定義し、それぞれをカプセル化して、互いに置き換え可能にします。このパターンにより、アルゴリズムの変化がアルゴリズムを利用するクライアントから独立します。

ある会社がアヒルゲームを開発しました。そこにはさまざまな特徴を持つアヒルが登場します:マガモ、アカガモ、ゴムアヒル……プログラムでこれらのアヒルをどう実装するでしょうか? このシナリオの問題を見ると、まずすべてのアヒルのスーパークラスDuckを定義し、共通のメソッドと属性をカプセル化することを思いつきやすいです。例えば、アヒルはみな泳げる、アヒルはそれぞれ見た目が異なる、などです。

实例

public abstract class Duck {
    public void swim(){
        System.out.println("All duck can swim!");
    }
    public abstract void display();
    public void fly(){
        System.out.println("飞~~~");
    }
    public void quack(){
        System.out.println("呱呱呱~");
    }
}

しかし、すぐにこのスーパークラスの定義には問題があることに気づきます。すべてのアヒルが飛べるわけではなく、すべてのアヒルが「ガーガー」と鳴くわけでもありません(ゴムアヒルは「キューキュー」と鳴くと仮定します)。このスーパークラスを継承するだけで、すべてのアヒルが飛べて、すべてのアヒルが「ガーガー」と鳴くことになってしまいます。

どうしよう?

Solution 1

最初に思いつく解決方法は、サブクラスでメソッドをオーバーライドすることです。簡単です。飛べないサブクラスでflyメソッドをオーバーライドして書き換えればいいのではないでしょうか?

しかし、欠点は明らかです。飛べないすべてのサブクラスでオーバーライドしなければならないことになります。仮に50種類のアヒルがすべて飛べないとしたら、書き換えの作業量とメンテナンス量はどれほどになるでしょうか?

Solution 2

では、2つ目の方法を考えましょう:さらにサブ抽象クラスを設計する方法です。例えば、飛べるが鳴かない抽象サブクラスFlyNoQuackDuck、鳴くが飛べない抽象サブクラスQuackNoFlyDuck、鳴かず飛べない抽象サブクラスNoQuackNoFlyDuck、飛べるし鳴ける抽象サブクラスFlyAndQuackDuck……などです。

書き進めていくうちに、この方法もダメだと気づきます。柔軟性に欠け、変化する部分が増えれば増えるほど、このような抽象サブクラスを定義しなければならなくなります。

Solution 3

では、どのような方法が良いのでしょうか?考えてみると、上記の問題が生じたのは、変化する部分を実装するために習慣的に継承を使おうとするからです。実際には、変化する部分を切り離して、コンポジション(合成)を用いて実装することができます。

ここでは3つの設計原則が使われている:

  • アプリケーション内で変化する可能性のある箇所を見つけ出し、それらを独立させます。
  • 実装ではなく、インターフェースに対してプログラミングする
  • 継承よりも合成を多用する

最初のデザイン原則を適用して、変化するメソッドfly()とquack()を独立させ、2つの振る舞いクラスインターフェースとしてカプセル化します。そして、さまざまな要件に応じて、インターフェースを実装した異なる振る舞いクラスを設計します。

2つ目と3つ目のデザイン原則を適用して、Duckクラスに2つのメンバ変数のインターフェースをコンポジションし、サブクラスで動的に値を代入します。

全体の「クラス図」は次のとおりです:

总结

この解決方法は私たちの問題を完璧に解決します。「ストラテジーパターン」の考え方を適用し、変化する部分を切り離してクラスに組み込み、異なるサブクラスに応じて異なる振る舞いサブクラスを「set」することで、振る舞いを動的に変えることを実現します。

代码实现

二つの行動インターフェースクラス

实例

public interface FlyBehavior {
    public void fly();
}

public interface QuackBehavior {
    public void quack();
}

飛行インターフェースを実装するさまざまな動作クラス

实例

public class FlyNoWay implements FlyBehavior{
    public void fly(){
        System.out.println("我不能飞……");
    }
}

public class FlyWithWings implements FlyBehavior{
    public void fly(){
        System.out.println("飞~~~");
    }
}

public class FlyWithRocket implements FlyBehavior{

    public void fly(){
        System.out.println("带上火箭筒,飞~~~");
    }
}

アヒルの鳴き声を実装するさまざまな動作クラス

实例

public class Quack implements QuackBehavior{
    public void quack(){
        System.out.println("呱呱呱~");
    }
}


public class Squeak implements QuackBehavior{
    public void quack(){
        System.out.println("吱吱吱~");
    }
}


public class MuteQuack implements QuackBehavior{
    public void quack(){
        System.out.println("我不会叫……");
    }
}

インターフェースを実装した動作クラスを組み込んだスーパークラス

实例

public abstract class Duck {
    protected FlyBehavior flyBehavior;
    protected QuackBehavior quackBehavior;

    public void swim(){
        System.out.println("All duck can swim!");
    }

    public abstract void display();

    /**
* 飛行行動を動的に変更
     */

    public void setFlyBehavior(FlyBehavior flyBehavior) {
        this.flyBehavior = flyBehavior;
    }

    /**
* アヒルの鳴き声行動を動的に変更
     */

    public void setQuackBehavior(QuackBehavior quackBehavior) {
        this.quackBehavior = quackBehavior;
    }

    public void performFly(){
        flyBehavior.fly();
    }

    public void performQuack(){
        quackBehavior.quack();
    }
}

異なるアヒルクラス

实例

/**
* 绿头鸭
 */

public class MallarDuck extends Duck{

    public MallarDuck() {
        //可飞
        flyBehavior = new FlyWithWings();
        //呱呱叫
        quackBehavior = new Quack();
    }

    @Override
    public void display() {
        System.out.println("マガモのように見える");
    }

}

/**
* 绿头鸭
 */

public class RedHeadDuck extends Duck{

    public RedHeadDuck() {
        //可飞
        flyBehavior = new FlyWithWings();
        //呱呱叫
        quackBehavior = new Quack();
    }

    @Override
    public void display() {
        System.out.println("ホシハジロのように見える");
    }

}

/**
* 橡皮鸭
 */

public class RubberDuck extends Duck{

    public RubberDuck() {
        //不会飞
        flyBehavior = new FlyNoWay();
        //吱吱叫
        quackBehavior = new Squeak();
    }

    @Override
    public void display() {
        System.out.println("ゴムアヒルのように見える");
    }

}

状态模式

ステートパターンは、オブジェクトが内部状態の変化に応じてその振る舞いを変えることを可能にします。オブジェクトはまるで自分のクラスを変更したかのように見えます。

ステートパターンはストラテジーパターンと非常に似ており、クラスの「状態」をカプセル化し、動作の実行時に自動的に遷移することで、クラスが異なる状態にあるときに同じ動作が異なる結果を示すようにします。ストラテジーパターンとの違いは、この遷移が「自動」であり、「意識されない」ことです。

状態パターンのクラス図は以下の通りです

ステートパターンのクラス図はストラテジーパターンとまったく同じです。違いはそれらの意図にあります。ストラテジーパターンはオブジェクトがどの戦略を使用するかを制御するのに対し、ステートパターンは状態を自動的に変更します。以下のケースを見れば明確になるはずです。

今、あなたの前にキャンディーマシンの要件があります。Javaで実装する必要があります。

分析してみましょう。キャンディーマシンの機能は、下の図に示すように4つのアクションと4つの状態に分けることができます。

状態が異なれば、同じ動作でも結果は異なります。例えば、「25セントを投入した」状態で「クランクを回す」と、キャンディが販売されます。一方、「25セントが入っていない」状態で「クランクを回す」と、先にコインを投入するよう促す表示が出ます。

簡単に考えた後、以下のようなキャンディーマシン実装コードを書きます。

实例

public class NoPatternGumballMachine{
    /*
* 四个状态
     */

    /**没有硬币状态*/
    private final static int NO_QUARTER = 0;
    /**投币状态*/
    private final static int HAS_QUARTER = 1;
    /**出售糖果状态*/
    private final static int SOLD = 2;
    /**糖果售尽状态*/
    private final static int SOLD_OUT = 3;

    private int state = SOLD_OUT;
    private int candyCount = 0;

    public NoPatternGumballMachine(int count) {
        this.candyCount = count;
        if(candyCount > 0)
            state = NO_QUARTER;
    }

    /*
* 四个动作
     */


    /**
* 投币
     */

    public void insertQuarter() {
        if(NO_QUARTER == state){
            System.out.println("投币");
            state = HAS_QUARTER;
        }
        else if(HAS_QUARTER == state){
            System.out.println("请不要重复投币!");
            returnQuarter();
        }
        else if(SOLD == state){
            System.out.println("已投币,请等待糖果");
            returnQuarter();
        }else if(SOLD_OUT == state){
            System.out.println("糖果已经售尽");
            returnQuarter();
        }
    }

    /**
* 退币
     */

    public void ejectQuarter() {
        if(NO_QUARTER == state){
            System.out.println("没有硬币,无法弹出");
        }
        else if(HAS_QUARTER == state){
            returnQuarter();
            state = NO_QUARTER;
        }
        else if(SOLD == state){
            System.out.println(“コインを返却できません。キャンディを配布中です。お待ちください”);
        }else if(SOLD_OUT == state){
            System.out.println("没有投币,无法退币");
        }
    }

    /**
* 转动出糖曲轴
     */

    public void turnCrank() {
        if(NO_QUARTER == state){
            System.out.println("请先投币");
        }
        else if(HAS_QUARTER == state){
            System.out.println("转动曲轴,准备发糖");
            state = SOLD;
        }
        else if(SOLD == state){
            System.out.println("已按过曲轴,请等待");
        }else if(SOLD_OUT == state){
            System.out.println("糖果已经售尽");
        }
    }

    /**
* 发糖
     */

    public void dispense() {
        if(NO_QUARTER == state){
            System.out.println("没有投币,无法发放糖果");
        }
        else if(HAS_QUARTER == state){
            System.out.println("this method don't support");
        }
        else if(SOLD == state){
            if(candyCount > 0){
                System.out.println("分发一颗糖果");
                candyCount --;
                state = NO_QUARTER;
            }
            else{
                System.out.println("抱歉,糖果已售尽");
                state = SOLD_OUT;
            }
        }else if(SOLD_OUT == state){
            System.out.println("抱歉,糖果已售尽");
        }
    }

    /**
* 退还硬币
     */

    protected void returnQuarter() {
        System.out.println("退币……");
    }

}

コードからわかるように、キャンディマシンはその時々の状態に応じて、対応する動作が異なる結果を示します。このコードは基本的な要件を満たしてはいますが、少し考えてみると、この実装コードは機能が複雑すぎて、拡張性が低く、オブジェクト指向のスタイルではないと感じるでしょう。

新しい要件により状態を1つ追加する必要があると仮定しましょう。その場合、すべてのアクションメソッドを修正し、else文を1つずつ追加する必要があります。また、要件が変更されて特定の状態でのアクションを修正する必要がある場合も、4つのメソッドを同時に変更しなければなりません。このような作業は煩雑で頭の痛いものになります。

どうすればよいのか? 六大設計原則の一つ

アプリケーション内で変化する可能性のある箇所を見つけ出し、それらを独立させます。

キャンディーマシンにおいて、状態こそが常に変化する部分であり、状態によってアクションは異なります。これを切り離すことがまさに可能です。

新しい設計アイデアは以下の通りです:

まず、Stateインターフェースを定義します。このインターフェース内では、キャンディーマシンの各アクションに対応するメソッドがあります。

次に、マシンの各状態に対して状態クラスを実装します。これらのクラスは、対応する状態でマシンの行動を実行する責任を持ちます。

最後に、古い条件分岐コードを排除し、その代わりに、アクションを状態クラスに委譲します。

Stateインターフェースを定義する

实例

public abstract class State {
    /**
* 投币
     */

    public abstract void insertQuarter();

    /**
* 退币
     */

    public abstract void ejectQuarter();

    /**
* 转动出糖曲轴
     */

    public abstract void turnCrank();

    /**
* 发糖
     */

    public abstract void dispense();

    /**
* 退还硬币
     */

    protected void returnQuarter() {
        System.out.println("退币……");
    }
}

マシンの各状態に対して状態クラスを実装する

实例

/**
* コインがない状態
 */

public class NoQuarterState extends State{
    GumballMachine gumballMachine;

    public NoQuarterState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
    @Override
    public void insertQuarter() {
        System.out.println("あなたは一枚のコインを投入した");
        //コイン有り状態に変換
        gumballMachine.setState(gumballMachine.hasQuarterState);
    }

    @Override
    public void ejectQuarter() {
        System.out.println("没有硬币,无法弹出");
    }

    @Override
    public void turnCrank() {
        System.out.println("请先投币");
    }

    @Override
    public void dispense() {
        System.out.println("没有投币,无法发放糖果");
    }

}

/**
* コイントスの状態
 */

public class HasQuarterState extends State{
    GumballMachine gumballMachine;

    public HasQuarterState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
    @Override
    public void insertQuarter() {
        System.out.println("请不要重复投币!");
        returnQuarter();
    }

    @Override
    public void ejectQuarter() {
        returnQuarter();
        gumballMachine.setState(gumballMachine.noQuarterState);
    }

    @Override
    public void turnCrank() {
        System.out.println("转动曲轴,准备发糖");
        gumballMachine.setState(gumballMachine.soldState);
    }

    @Override
    public void dispense() {
        System.out.println("this method don't support");
    }

}

/**
* 販売中の状態
 */

public class SoldState extends State{
    GumballMachine gumballMachine;

    public SoldState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }

    @Override
    public void insertQuarter() {
        System.out.println("已投币,请等待糖果");
        returnQuarter();
    }

    @Override
    public void ejectQuarter() {
        System.out.println(“コインを返却できません。キャンディを配布中です。お待ちください”);
    }

    @Override
    public void turnCrank() {
        System.out.println("已按过曲轴,请等待");
    }

    @Override
    public void dispense() {
        int candyCount = gumballMachine.getCandyCount();
        if(candyCount > 0){
            System.out.println("分发一颗糖果");
            candyCount--;
            gumballMachine.setCandyCount(candyCount);
            if(candyCount > 0){
                gumballMachine.setState(gumballMachine.noQuarterState);
                return;
            }
        }

        System.out.println("抱歉,糖果已售尽");
        gumballMachine.setState(gumballMachine.soldOutState);
    }

}

/**
* 売り切れの状態
 */

public class SoldOutState extends State{
    GumballMachine gumballMachine;

    public SoldOutState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
    @Override
    public void insertQuarter() {
        System.out.println("糖果已经售尽");
        returnQuarter();
    }

    @Override
    public void ejectQuarter() {
        System.out.println("没有投币,无法退币");
    }

    @Override
    public void turnCrank() {
        System.out.println("糖果已经售尽");
    }

    @Override
    public void dispense() {
        System.out.println("糖果已经售尽");
    }

}

キャンディマシンの動作を状態クラスに委譲する

实例

public class GumballMachine extends State{
    public State noQuarterState = new NoQuarterState(this);
    public State hasQuarterState = new HasQuarterState(this);
    public State soldState = new SoldState(this);
    public State soldOutState = new SoldOutState(this);

    private State state = soldOutState;
    private int candyCount = 0;

    public GumballMachine(int count) {
        this.candyCount = count;
        if(count > 0)
            setState(noQuarterState);
    }

    @Override
    public void insertQuarter() {
        state.insertQuarter();
    }
    @Override
    public void ejectQuarter() {
        state.ejectQuarter();
    }
    @Override
    public void turnCrank() {
        state.turnCrank();
    }
    @Override
    public void dispense() {
        state.dispense();
    }

    public void setState(State state) {
        this.state = state;
    }

    public State getState() {
        return state;
    }

    public void setCandyCount(int candyCount) {
        this.candyCount = candyCount;
    }

    public int getCandyCount() {
        return candyCount;
    }

}

この設計では、キャンディーマシンは状態の変化を把握する必要がまったくなく、状態のメソッドを呼び出すだけでよいことがわかります。状態の変化は状態内部で発生します。これが「ステートパターン」です。

もしここでさらに状態を追加する場合、キャンディマシンは何も変更する必要はありません。状態クラスをもう1つ追加し、関連する状態クラスのメソッド内に遷移の処理を追加するだけでよいのです。

理解できない?ここにステートパターンに関する記事もあります:https://dzone.com/articles/state-pattern-simplified

原文链接:https://blog.csdn.net/z55887/article/details/73198039

原文链接:https://blog.csdn.net/z55887/article/details/60608898