Stateパターンで考える依存数の軽減

n個の状態と状態により動作が変化するm個のメソッドを持つオブジェクトを考える。

初歩的な処理

初歩的な処理では、全てのメソッドに対し、状態の数だけ分岐条件を記述する。

このとき、オブジェクトは状態に対し、m * n本だけ常に依存している。オブジェクトはその状態に関わらず他の状態の結果を返す準備があり、他の状態の処理を返さないような動作である保証はそれぞれm個のメソッドに分散され、管理される。


Stateパターン

一方、Stateパターンでは、全てのメソッドに対し、持っているStateオブジェクトのメソッドを透過的に呼ぶ。

このとき、オブジェクトが管理するStateは常に一つなので、ある時点でのオブジェクトは状態に対し、m本だけ依存している。また、オブジェクトは他の状態を同時に管理しないため、他の状態の処理を返さないような動作である保証をする必要はない。オブジェクトが取りうるStateはn個なので、オブジェクトはn + m本だけ、Stateに依存している。

つまり、Stateパターンの導入によりオブジェクトから状態への依存の数が軽減でき、状態の追加や削除に強く、ある状態での動作の管理に適した設計となる。

蛇足

…って感じ?「依存の種類」での考察はないのが自分ではアレではあるけど。2つの要素を同時に語ってしまってる感もあるし。
19.State パターン | TECHSCORE(テックスコア)を例に考えていただければ。

図を追記した。デザインパターンは一見構造が複雑だけど依存数が減るってのがわかると思う。

図では状態とオブジェクトを別領域に記述してみたけど、違和感がない。考えてみれば、「あるクラスがとりうる状態」は静的モデルだよね。特定のオブジェクトには関係なく、また状態はそれ自体振舞いではない。

矢印の数が試験対象であり仕様/実装バグの可能性であるとすると、状態とそれに関連する動作の数が増えるほどStateパターンは有用だということがわかる。また、「オブジェクトはある状態を持つ」という抽象的な仕様が表現され、抽象的なレベルでの試験が可能であるって利点はもの凄く大きいように思う。

ElementaryPattern コード例

  • enumにより古典的なint定数を使うより安全性は向上してるが、各メソッドでの実装漏れの心配があるため、default throw new IllegalStateException が無難。
public class ElementaryPattern {
    public enum State {
        STATE1, STATE2, STATE3
    }
    
    private State state;
    public void setState(State state) {this.state = state;}
    
    public void method1() {
        switch (state) {
        case STATE1:
            System.out.println("Angela Aki");
            break;
        case STATE2:
            System.out.println("Brother Tom");
            break;
        case STATE3:
            System.out.println("Charlie Hama");
            break;
        default:
            throw new IllegalStateException();
        }
    }
    
    public void method2() {
        switch (state) {
        case STATE1:
            System.out.println("Hogehoge");
            break;
        case STATE2:
            System.out.println("Fugafuga");
            break;
        case STATE3:
            System.out.println("FooBar");
            break;
        default:
            throw new IllegalStateException();
        }
    }
}

StatePattern コード例

  • 行数はElementaryPatternに比べ、4行増し。ほとんど増えてないし、多分減りもしない。オブジェクト指向言語ではステップ数と保守性は別問題な証拠。
public class StatePattern {
    private State state;
    public void setState(State state) {this.state = state;}
    
    public void method1() {
        state.method1();
    }
    
    public void method2() {
        state.method2();
    }
}

interface State {
    public void method1();
    public void method2();
}

class State1 implements State {
    public void method1() {
        System.out.println("Angela Aki");
    }
    public void method2() {
        System.out.println("Hogehoge");
    }
}

class State2 implements State {
    public void method1() {
        System.out.println("Brother Tom");
    }
    public void method2() {
        System.out.println("Fugafuga");
    }
}

class State3 implements State {
    public void method1() {
        System.out.println("Charlie Hama");
    }
    public void method2() {
        System.out.println("FooBar");
    }
}