状態遷移図、状態遷移表 - 要求分析駆動設計
状態遷移図、状態遷移表から作成するStateクラスはDDDではValueObjectに分類されるだろう。 Stateの目的はラッパーValueObjectとしての役割とある状態から別の状態に遷移する関係を制御することにある。
モデリングの例
コードとの対応
ファイルパスは以下の通りにする。
app
└── domains
├── bought_items_feature
│ └── xxx_state.rb
└── states
└── xxx_state.rb
StateクラスもEntity同様、関連する機能のfeatureディレクトリ内に入れる。あまりないと思うが、特定の機能に依存しない普遍的なStateがあるのであれば、domains
ディレクトリ直下か、domains/states
ディレクトリにまとめて格納する。
コード例
class TaskStatusValueObject
attr_accessor :task_status
IN_HAND = 1
SUSPEND = 2
FROZEN = 3
def initialize(task_status)
self.task_status = task_status
end
# 着手状態か?
def in_hand?
task_status == IN_HAND
end
end
class TaskState
attr_accessor :progress_status, :task_status
def initialize(progress_status, task_status)
self.progress_status = progress_status
self.task_status = task_status
end
# 実行状態にできるか?(次の状態の中に実行状態はあるか?)
def work_in_next?
return false unless task_status_value_object.in_hand?
return false unless progress_status_value_object.exploration? # 調査状態か?
true
end
# 実行状態にする
def to_work
raise '不正な状態遷移' unless work_in_next?
self.class.new(ProgressStatusValueObject::WORK, task_status)
end
private
def task_status_value_object
@task_status_value_object ||= TaskStatusValueObject.new(task_status)
end
end
# タスクの永続化用エンティティ
class MutableTask
attr_accessor :raw
delegate :progress_status=, :task_status=, to: :raw
def initialize(raw)
self.raw = raw
@task_state = {}
end
# Entityを実行状態にする
def shift_to_work_state
return false if task_state.work_in_next?
new_state = task_state.to_work
# 状態変更をStateからEntityに反映させる
self.progress_status = new_state.progress_status
self.task_status = new_state.task_status
true
end
private
# Entity内でStateが変化するのでメモ化を工夫している
def task_state
@task_state[progress_status] ||= {}
@task_state[progress_status][task_status] ||= TaskState.new(progress_status, task_status)
end
end
このコード例では、StateとValueObjectを分けたが、単純なら分けなくとも良い。不正な状態遷移を表現する例外クラスを作成しても良い。
依存関係
Ruleと基本的に同じなので、いくらか省略する。 StateはView、Helper、Controller、UseCase、Service、Entity、Rule、他のStateから依存されうる。他のStateに依存されていても、他から依存されてよい。 StateはValueObject、Event、他のStateに依存できる。 Entity同様、Featureを跨いでの依存はしてはならない。親FeatureのStateに依存することは許される。 ただ、親FeatureにStateを作ることや他のStateに依存することが妥当な場合がどの程度存在するのか、私は詰め切れていない。
Eventへの依存については、状態遷移時にユーザーに通知したい場合などに発生する。
TaskState#to_work
メソッド内で新しいTaskState
のインスタンスを生成しているが、ここでコンストラクタ引数から当該イベントのインスタンスを渡す方法がある。
詳しくは後述のイベントを参照。
状態遷移図、状態遷移表を作成するときしないとき、Stateを作るとき作らないとき
状態遷移図、状態遷移表を作成するのは、いうまでもなく状態とその遷移関係をまとめたい場合である。 状態遷移図、状態遷移表の作成が必要なら、Stateは作成するべきである。 単に状態を管理するだけで、各状態への遷移に制限がないなら、ValueObjectで十分である。 しかし、遷移に制限がないのが偶然という場合もあり、後になってStateクラスが適当だったと判明する場合もある。 十分に考えて判別するか、遷移の制限の有無にかかわらず最初からStateクラスとして定義してしまう方法もある。
ドキュメンテーション
状態遷移は状態遷移図か状態遷移表でまとめる。状態遷移は文章で適切に記述するのが難しいものの一つである。 文章で書くこと自体は可能だが、遷移関係を把握することや状態や遷移関係の漏れと矛盾を発見するのが図よりも難しくなる。 簡単な関係でも状態遷移図か状態線表としてまとめることをおすすめする。