デザインパターンを学んでいると、ほぼ一度は壁にぶつかるポイントがあります。
それがStrategyパターンとStateパターンです。
UML(Unified Modeling Language、統一モデリング言語)の図を並べて見ると、ほとんどコピー&ペーストしたように同じです。インターフェースが1つあり、その実装を複数作り、コンテキストが保持します。
「これ、名前が違うだけでは?」と思うのも無理はありません。
まず結論から言います。2つのパターンを分けるのは構造ではなく意図です。Strategyは外からアルゴリズムを差し替え、Stateは内部で状態自身が次の状態へ遷移します。
この一文を押さえて読み進めれば、もう混同しないはずです。
状態オブジェクトで実際の条件分岐を取り除く実装は、Swift Stateパターンのリファクタリングで段階的に解説しています。
なぜ構造がこれほど同じなのでしょうか?
どちらもGoF(Gang of Four)のデザインパターン本に登場する振る舞いパターンです。
どちらも同じ材料を使います。共通インターフェース、それを実装する複数のクラス、そしてそれらを保持するコンテキストです。
そのため、コードだけを見ると本当に区別できません。
Strategyパターンをコードで表すと、次のようになります。
protocol PaymentStrategy {
func pay(_ amount: Int)
}
struct CardPayment: PaymentStrategy {
func pay(_ amount: Int) { print("カードで \(amount)ウォン決済") }
}
struct KakaoPay: PaymentStrategy {
func pay(_ amount: Int) { print("Kakao Payで \(amount)ウォン決済") }
}
final class Checkout {
var strategy: PaymentStrategy
init(strategy: PaymentStrategy) { self.strategy = strategy }
func pay(_ amount: Int) { strategy.pay(amount) }
}
let checkout = Checkout(strategy: CardPayment())
checkout.pay(10000)
checkout.strategy = KakaoPay() // 外から差し替え
checkout.pay(5000)
// 出力:
// カードで 10000ウォン決済
// Kakao Payで 5000ウォン決済
ここで注目すべきは、strategyを差し替える主体です。外側のクライアントコードです。
カードからKakao Payへ支払い方法を変えたのは、開発者、つまり外側にいる誰かです。
Strategy自身は次に何が来るかをまったく知りません。関心もありません。
では、Stateパターンは何が違うのでしょうか?
Stateパターンでは、状態自身が状態遷移を決定します。
信号機を思い浮かべてください。赤信号は次が青信号だと知っていて、青信号は次が黄色信号だと知っています。
つまり、次の状態へ移るルールが各状態の中に入っています。
protocol TrafficState {
func next(_ light: TrafficLight)
}
final class Red: TrafficState {
func next(_ light: TrafficLight) {
print("赤 → 青")
light.state = Green() // 状態が自ら次を指定
}
}
final class Green: TrafficState {
func next(_ light: TrafficLight) {
print("青 → 黄")
light.state = Yellow()
}
}
final class Yellow: TrafficState {
func next(_ light: TrafficLight) {
print("黄 → 赤")
light.state = Red()
}
}
final class TrafficLight {
var state: TrafficState = Red()
func change() { state.next(self) }
}
let light = TrafficLight()
light.change()
light.change()
light.change()
// 出力:
// 赤 → 青
// 青 → 黄
// 黄 → 赤
違いが見えてきましたか?
Strategyパターンでは、checkout.strategy = KakaoPay()のように外から直接差し替えました。
Stateパターンでは、light.state = Green()を状態クラス自身が行います。クライアントはchange()を呼ぶだけです。
まさにここが核心です。
Strategyは外から差し替え、Stateは内側で自ら遷移します。
決定的な違いを3つに整理
言葉だけではまた混乱するので、表にまとめます。
| 区分 | Strategyパターン | Stateパターン |
|---|---|---|
| 意図 | アルゴリズムを交換 | 状態に応じて振る舞いを変える |
| 遷移の主体 | 外部クライアント | 状態オブジェクト自身 |
| オブジェクト間の関係 | 互いを知らない(独立) | 互いを知り、参照する |
| ライフサイクル | 通常は一度決めたら維持 | 実行中も変化し続ける |
特に2行目と3行目を覚えておいてください。
Strategy同士は互いの存在を知りません。カード決済がKakao Payを知る必要はありません。
一方、State同士は互いを知る必要があります。赤信号が青信号オブジェクトを直接生成して渡すからです。
この「互いを知っているかどうか」が、コード上で2つのパターンを見分ける最も確実な手がかりです。
いつ使い、いつ避けるべきでしょうか?
実務で私が判断するときの基準をまとめると、次のとおりです。
| 状況 | 判断 |
|---|---|
| 同じことを行う方法だけが複数ある場合(ソート、決済、圧縮) | Strategyパターン |
| オブジェクトが状況に応じて異なる振る舞いをし、その状況が変わる場合 | Stateパターン |
| 遷移ルールが複雑なif-elseの塊になっている場合 | Stateパターンで解決 |
| 関数を1つ渡すだけで済むほど単純な場合 | どちらも過剰、クロージャを使う |
注意点もあります。
状態が2~3個だけで遷移も単純なら、無理にパターンを導入せず、enumとswitchで十分なことが多いです。
パターンは複雑さを制御する道具であって、単純なコードに形式を与える飾りではありません。
面接ではこう聞かれます
Q. StrategyパターンとStateパターンは構造が同じですが、どう区別しますか?
構造はほぼ同じですが、意図が異なります。Strategyは外部からアルゴリズムを交換するためのもので、Stateは内部状態に応じて振る舞いを変えるためのものです。決定的に異なるのは遷移の主体で、Strategyではクライアントが、Stateでは状態オブジェクト自身が次へ進みます。
Q. Stateパターンでは状態同士が互いを参照しますが、問題ありませんか?
状態間に結合が生じるのは確かです。そのため、遷移ロジックを状態内に置く代わりに、コンテキストや別の遷移テーブルへ切り出して結合を下げることもあります。状態が増え、遷移が複雑に絡み合うなら、ステートマシンライブラリを使う方法もあります。
図が同じように見えても怖がらないでください。「誰が遷移するのか」だけを尋ねれば、答えはすぐ出ます。
外から差し替えればStrategy、内側で自ら遷移すればState。この一文だけ覚えて帰れば、今日の記事は成功です。また混同しやすいパターンを取り上げます。

