1概要

このチュートリアルでは、動作上のGoFデザインパターンの1つ、St​​ateパターンを紹介します。

最初に、その目的の概要とそれが解決しようとしている問題について説明します。それでは、州のUML図と実際の例の実装を見ていきます。


2状態デザインパターン

Stateパターンの主な考え方は、クラスを変更せずにオブジェクトの動作を変更できるようにすることです。


また、それを実装することによって、コードは多くのif/else文がなくてもきれいに保たれるはずです。

郵便局に送られる荷物があるとします。荷物自体を注文してから郵便局に配送し、最終的にクライアントが受け取ることができます。今、実際の状態に応じて、我々はその配達状況を印刷したいです。

最も簡単な方法は、いくつかのブールフラグを追加し、クラス内の各メソッド内に単純なif/elseステートメントを適用することです。単純なシナリオではそれほど複雑にはなりません。しかし、処理する状態が増えると、コードが複雑になり、汚染される可能性があります。その結果、if/elseステートメントがさらに増えます。

その上、各状態のためのすべての論理はすべての方法にわたって広がるでしょう。さて、ここがStateパターンが使うと考えられるところです。ステートデザインパターンのおかげで、ロジックを専用クラスにカプセル化し、https://en.wikipedia.org/wiki/Single

responsibility

principle[シングル責任原則]とhttps://en.wikipedia.org/wiki/Openを適用することができます%E2%80%93closed__principle[開放/閉鎖原理]には、よりクリーンで保守しやすいコードがあります。


3 UML図

UML図では、

Context

クラスには

State

が関連付けられており、これはプログラムの実行中に変更されます。

私たちのコンテキストは、その動作を状態の実装に委譲することです。つまり、すべての着信要求は、状態の具体的な実装によって処理されます。

ロジックが分離されており、新しいステートを追加するのは簡単です – 必要に応じて別の

State

実装を追加することになります。


4実装

アプリケーションを設計しましょう。すでに述べたように、パッケージは注文して配達して受け取ることができるので、3つの状態とコンテキストクラスがあります。

まず、コンテキストを定義しましょう。それが

Package

クラスになります。

public class Package {

    private PackageState state = new OrderedState();

   //getter, setter

    public void previousState() {
        state.prev(this);
    }

    public void nextState() {
        state.next(this);
    }

    public void printStatus() {
        state.printStatus();
    }
}

ご覧のとおり、状態を管理するための参照が含まれています。

previousState()、nextState()、およびprintStatus()

メソッドがあり、ここでジョブを状態オブジェクトに委任しています。状態は互いにリンクされ、

すべての状態は両方のメソッドに渡された

this

reference

に基づいて別の状態を設定します。

クライアントは

Package

クラスと対話しますが、状態の設定を処理する必要はなく、クライアントは次の状態または前の状態に進むだけで済みます。

次に、次のシグネチャを持つ3つのメソッドを持つ

PackageState

を用意します。

public interface PackageState {

    void next(Package pkg);
    void prev(Package pkg);
    void printStatus();
}

このインタフェースは、各具象状態クラスによって実装されます。

最初の具象状態は

OrderedState

になります。

public class OrderedState implements PackageState {

    @Override
    public void next(Package pkg) {
        pkg.setState(new DeliveredState());
    }

    @Override
    public void prev(Package pkg) {
        System.out.println("The package is in its root state.");
    }

    @Override
    public void printStatus() {
        System.out.println("Package ordered, not delivered to the office yet.");
    }
}

ここでは、パッケージの注文後に発生する次の状態を示します。順序付けられた状態は私たちのルート状態であり、明示的にマークします。

どちらの方法でも、状態間の遷移がどのように処理されるかがわかります。


DeliveredState

クラスを見てみましょう。

public class DeliveredState implements PackageState {

    @Override
    public void next(Package pkg) {
        pkg.setState(new ReceivedState());
    }

    @Override
    public void prev(Package pkg) {
        pkg.setState(new OrderedState());
    }

    @Override
    public void printStatus() {
        System.out.println("Package delivered to post office, not received yet.");
    }
}

繰り返しになりますが、州間のリンクがあります。パッケージはその状態を発注済みから配達済みに変更しています、

printStatus()

内のメッセージも同様に変更されます。

最後のステータスは

ReceivedState

です。

public class ReceivedState implements PackageState {

    @Override
    public void next(Package pkg) {
        System.out.println("This package is already received by a client.");
    }

    @Override
    public void prev(Package pkg) {
        pkg.setState(new DeliveredState());
    }
}

これが最後の状態に到達する場所です。前の状態にロールバックすることしかできません。

一方の国が他方の国について知っているので、すでにある程度の利益があることがわかります。我々はそれらを密接に結合させています。


5テスト中

実装がどのように動作するかを見てみましょう。まず、セットアップの移行が予想どおりに機能するかどうかを確認しましょう。

@Test
public void givenNewPackage__whenPackageReceived__thenStateReceived() {
    Package pkg = new Package();

    assertThat(pkg.getState(), instanceOf(OrderedState.class));
    pkg.nextState();

    assertThat(pkg.getState(), instanceOf(DeliveredState.class));
    pkg.nextState();

    assertThat(pkg.getState(), instanceOf(ReceivedState.class));
}

それでは、私たちのパッケージがその状態に戻ることができるかどうかを簡単に確認してください。

@Test
public void givenDeliveredPackage__whenPrevState__thenStateOrdered() {
    Package pkg = new Package();
    pkg.setState(new DeliveredState());
    pkg.previousState();

    assertThat(pkg.getState(), instanceOf(OrderedState.class));
}

その後、状態を変更し、

printStatus()

メソッドの実装が実行時にその実装をどのように変更するかを確認しましょう。

public class StateDemo {

    public static void main(String[]args) {

        Package pkg = new Package();
        pkg.printStatus();

        pkg.nextState();
        pkg.printStatus();

        pkg.nextState();
        pkg.printStatus();

        pkg.nextState();
        pkg.printStatus();
    }
}

これにより、次のような出力が得られます。

Package ordered, not delivered to the office yet.
Package delivered to post office, not received yet.
Package was received by client.
This package is already received by a client.
Package was received by client.

コンテキストの状態を変えてきたので、振る舞いは変わりましたが、クラスは変わりません。私たちが利用するAPIと同様に。

また、状態間の遷移が発生し、私たちのクラスはその状態を変化させ、結果的にその動作を変化させました。


6. 欠点

状態パターンの欠点は、状態間の移行を実装するときの見返りです。これは状態をハードコード化します。これは一般的に悪い習慣です。

しかし、私たちのニーズや要件によっては、それが問題になる場合もあれば、そうでない場合もあります。


7. 州対戦略パターン

両方のデザインパターンは非常に似ていますが、それらのUMLダイアグラムは同じですが、それらの背後にある考え方はわずかに異なります。

まず、


ストラテジーパターン

は、交換可能なアルゴリズムのファミリー

を定義します。一般的に、それらは同じ目的を達成しますが、異なる実装、例えばソートやレンダリングのアルゴリズムを使用します。

  • 状態パターンでは、実際の状態に基づいて、動作が** 完全に変わる可能性があります。

次に、

戦略では、クライアントはそれらを使用して明示的に変更するために考えられる戦略を知っておく必要があります

状態パターンでは、各状態は別の状態にリンクされ、有限ステートマシンのようにフローを作成します。


8結論

状態設計パターンは、プリミティブなif/elseステートメントを避けたい場合に最適です。代わりに、ロジックを別々のクラスに抽出し、コンテキストオブジェクトに動作をステートクラスに実装されたメソッドに委譲させます。その上、私たちは状態間の遷移を利用することができます、そこで1つの状態は文脈の状態を変えることができます。

一般に、このデザインパターンは比較的単純なアプリケーションに最適ですが、より高度な方法としては、https://www.baeldung.com/spring-state-machine[SpringのState Machineチュートリアル]をご覧ください。

いつものように、完全なコードはhttps://github.com/eugenp/tutorials/tree/master/patterns/design-patterns[GitHubプロジェクト]にあります。