Stateパターン
概要
状態による処理の分岐が複数あるときに、処理内で分岐させるのではなく、クラスを切り替えることで振る舞いを変えるデザインパターン。
特に、状態による分岐がある関数が複数あると向いている
使い方
bad
class User {
private status: 'not_signed_up' | 'active' | 'deleted' = 'not_signed_up'
constructor(
public name: string,
){}
post(post: Post) {
if (status == 'not_signed_up') console.log('会員登録をしてください')
else if (status == 'deleted') console.log('削除されたアカウントです')
else post.publish()
}
deleteAccount() {
if (this.status == 'not_signed_up') this.status = 'deleted'
else if (this.status == 'deleted') console.log('すでに削除されています')
else this.status = 'deleted'
}
}
statusが変わるとpostやdeleteAccountの処理内容が変わる。
分岐が増えるとあるstatusの時の処理を探すのも大変になるし、関数が増えるとその分分岐が複数箇所に跨るため結合度が高くなってしまう
good
interface UserState {
post: (post: Post) => void;
deleteAccount: (user: User) => void;
}
class NotSignedUpUserState extends UserState {
post(post: Post) {
console.log('会員登録をしてください')
}
deleteAccount(user: User) {
user.setState(new DeletedUserState());
}
}
class ActiveUserState extends UserState {
post(post: Post) {
post.publish()
}
deleteAccount(user: User) {
user.setState(new DeletedUserState());
}
}
class DeletedUserState extends UserState {
post(post: Post) {
console.log('削除されたアカウントです')
}
deleteAccount(user: User) {
console.log('すでに削除されています')
}
}
class User {
private state: UserState = new NotSignedUpUserState();
constructor(
public name: string,
){}
setState(state: UserState) {
this.state = state;
}
post(post: Post) {
return state.post(post);
}
deleteAccount() {
return state.deleteAccount(this);
}
}
状態ごとの処理をクラスでstateに分離したことによって、どの状態の時にどの処理を行うかがわかりやすくなった。
Stateが増えても分岐が増えないので可読性が上がる。
Strategyパターンとの違い
Strategyパターンも処理を注入するという同じ構造のデザインパターンになっている。
しかし以下のポイントで違いがある
- 注入するインスタンスの役割
- State: そのインスタンス自体が状態を表す。それにロジックがついてくる。
- Strategy: そのインスタンスはロジックしか表現しない
- 結合度
- State: 状態が遷移する時、遷移先を知っている必要があるためその部分での結合が少しある
- Strategy: 純粋なロジックなので相互に存在を知らない。かなり疎結合
- インスタンス切り替え方
- State: State内で別のStateへ差し替える
- Strategy: 完全に外から注入する
使い所
- クラスなどが状態によって処理の分岐が多い時
- その状態の数が多い時
- 似たような状態の分岐が多い時