Javaのステートパターン:状態のカプセル化による動作の動的な強化
約2分
別名
- 状態を表すオブジェクト
ステートデザインパターンの目的
内部状態の変化に応じてオブジェクトの振る舞いを動的に変更し、Javaアプリケーションの応答性を最適化します。
現実世界の例を用いたステートパターンの詳細な説明
現実世界の例
交差点の信号機システムを考えてみましょう。信号機は、緑、黄、赤の3つの状態のいずれかになります。現在の状態に応じて、信号機の動作は変化します。
- **緑の状態**:車は交差点を通過できます。
- **黄色の状態**:車はすぐに赤信号に変わることの警告を受け、停止の準備をする必要があります。
- **赤の状態**:車は停止し、信号が青に変わるのを待つ必要があります。
このシナリオでは、信号機はステートデザインパターンを使用しています。各状態(緑、黄、赤)は、その特定の状態で行われることを定義する異なるオブジェクトによって表されます。信号機(コンテキスト)は、動作を現在の状態オブジェクトに委任します。状態が変化すると(たとえば、緑から黄に)、信号機はその状態オブジェクトを更新し、それに応じて動作を変更します。
簡単な言葉で
ステートパターンを使用すると、オブジェクトは動作を変更できます。
Wikipediaによると
ステートパターンは、内部状態が変化したときに対象の振る舞いを変えることを可能にする振る舞いソフトウェア設計パターンです。このパターンは、有限状態マシンの概念に近いものです。ステートパターンは、パターンのインターフェースで定義されたメソッドの呼び出しを通じて戦略を切り替えることができる、ストラテジーパターンとして解釈できます。
Javaにおけるステートパターンのプログラム例
プログラム例では、気分が交互に変わるマンモスが登場します。
まず、状態インターフェースとその具体的な実装を示します。
public interface State {
void onEnterState();
void observe();
}
@Slf4j
public class PeacefulState implements State {
private final Mammoth mammoth;
public PeacefulState(Mammoth mammoth) {
this.mammoth = mammoth;
}
@Override
public void observe() {
LOGGER.info("{} is calm and peaceful.", mammoth);
}
@Override
public void onEnterState() {
LOGGER.info("{} calms down.", mammoth);
}
}
@Slf4j
public class AngryState implements State {
private final Mammoth mammoth;
public AngryState(Mammoth mammoth) {
this.mammoth = mammoth;
}
@Override
public void observe() {
LOGGER.info("{} is furious!", mammoth);
}
@Override
public void onEnterState() {
LOGGER.info("{} gets angry!", mammoth);
}
}
次に、状態を含むマンモスを示します。状態は `timePasses` メソッドの呼び出しによって変化します。
public class Mammoth {
private State state;
public Mammoth() {
state = new PeacefulState(this);
}
public void timePasses() {
if (state.getClass().equals(PeacefulState.class)) {
changeStateTo(new AngryState(this));
} else {
changeStateTo(new PeacefulState(this));
}
}
private void changeStateTo(State newState) {
this.state = newState;
this.state.onEnterState();
}
@Override
public String toString() {
return "The mammoth";
}
public void observe() {
this.state.observe();
}
}
マンモスが時間の経過とともにどのように動作するかの完全な例を次に示します。
public static void main(String[] args) {
var mammoth = new Mammoth();
mammoth.observe();
mammoth.timePasses();
mammoth.observe();
mammoth.timePasses();
mammoth.observe();
}
プログラム出力
The mammoth gets angry!
The mammoth is furious!
The mammoth calms down.
The mammoth is calm and peaceful.
Javaでステートパターンを使用する場合
- オブジェクトの動作はその状態に依存し、その状態に応じて実行時に動作を変更する必要があります。
- 操作には、オブジェクトの状態に依存する、大きく複数部分からなる条件文があります。
Javaにおけるステートパターンの実際のアプリケーション
- Javaのコレクションフレームワークの `java.util.Iterator` は、反復に異なる状態を使用します。
- ネットワークプログラミングのTCP接続クラスは、多くの場合、`確立`、`リッスン`、`クローズ` などの状態を実装します。
ステートパターンの利点と欠点
利点
- 状態固有の動作をローカライズし、異なる状態の動作を分割します。
- 状態遷移を明示的にします。
- 再利用可能な状態オブジェクトは、Javaのさまざまなコンテキスト間で効率的に共有できるため、メモリ管理とパフォーマンスが向上します。
欠点
- 状態を表すクラスが多数になる可能性があります。
- コンテキストクラスは、状態遷移ロジックによって複雑になる可能性があります。
関連するJavaデザインパターン
- フライウェイト:状態オブジェクトは、異なるコンテキスト間で共有される場合があります。
- シングルトン:状態オブジェクトは、多くの場合シングルトンです。
- ストラテジー:どちらのパターンも構造は似ていますが、ステートパターンの実装はコンテキストの状態に依存します。