ioでデザインパターン stateパターン編

「編」とか言って、続くかどうかわからないけどねー。

iolanguageは複数のプロトタイプオブジェクトを管理することによって、多重継承を実現しています。また、プロトタイプオブジェクトは追加したり削除したりできます。ということで、Stateパターンが簡単に実現できたりします。

オブジェクトの状態によってスロット(=メソッド)を追加したり削除したりして、受け付けるメッセージを動的に変えたいってのが基本的な動き。で、これを実現するために、appendSlot/removeSlotでオブジェクトに直接スロットを追加/削除する方法もあるけど、状態遷移によるスロットの追加/削除/変更が複数になれば、状態遷移操作の記述が複雑で冗長になる。そこで、Stateオブジェクトをプロトタイプオブジェクトとして迎え、状態遷移はプロトタイプオブジェクトの変更のみを行い、状態固有のスロットはStateオブジェクトに委譲しようという作戦です。うん、プロトタイプ指向っぽいぞ。

クラス指向の場合、あるクラスオブジェクトとして生成されたインスタンスは、事後にクラスを変更することはできない。一方でプロトタイプ指向の場合、インスタンスはそのプロトタイプを変更することで、クラスチェンジのような動作をすることができるわけです。

test.io

  • Contextタイプのインスタンスが、状態としてStateA, StateB, StateCを持つ。
  • 初期状態はStateA。
  • インスタンスのstatusスロットに問い合わせることによって、そのインスタンスの状態がわかる。
  • 状態遷移は(StateA) ←→ (StateB) ←→ (StateC)。
  • インスタンスのto(A|B|C)スロットを呼び出すことによって、状態が遷移する。
  • 状態遷移のスロットは、インスタンスが持つ状態によって変わる。
StateA := Object clone do(
    status := "A"
    
    toB := method(
        self removeProto(StateA)
        self appendProto(StateB)
        self 
    )
)

StateB := Object clone do(
    status := "B"
    
    toA := method(
        self removeProto(StateB)
        self appendProto(StateA)
        self 
    )
    toC := method(
        self removeProto(StateB)
        self appendProto(StateC)
        self 
    )
)

StateC := Object clone do(
    status := "C"
    
    toB := method(
        self removeProto(StateC)
        self appendProto(StateB)
        self 
    )
)

Context := Object clone do(
    init := method(
        self appendProto(StateA)
    )
)

コンソールで実行してみよう!

Io> doFile("test.io")
==>  Context_0x60bb68:
  init             = method(...)
  type             = "Context"

Io> con := Context clone
==>  Context_0x4dd2b0:

Io> con status         #初期状態はA
==> A
Io> con toB            #Bへ遷移!
==>  Context_0x4dd2b0:

Io> con status         #B状態になった!
==> B
Io> con toC            #Cへ遷移!
==>  Context_0x4dd2b0:

Io> con status         #C状態になった!
==> C
Io> con toA            #Aへ遷移…できない!

  Exception: Context does not respond to 'toA'
  ---------
  Context toA                          Command Line 1

Io> con hasSlot("toA") #状態Cなので、Aへ遷移するためのスロットはない
==> false
Io> con hasSlot("toB") #状態Cなので、Bへ遷移するためのスロットがある
==> true
Io> con hasSlot("toC") #状態Cなので、Cへ遷移するためのスロットはない
==> false
Io>

蛇足

JavaでStateパターンが今一つ使えない理由に、メソッド名が固定されるってのがあると思います。

例えば、「承認待ち」状態から「承認」状態へと遷移するか「却下」状態へと遷移するか、メソッド名で区別したいですよね。「承認する」メソッドと「却下する」メソッドと。じゃぁ、全状態で持ちうる遷移のメソッド名を全て用意して、処理を許可するメソッド以外はIllegalStateExceptionを投げるようにするか。「承認する」「修正する」「却下する」「取下げる」「送付する」…。でも、その処理が許可されているかどうか実行しなきゃわからないのは使いにくい。処理が許可されているか返すメソッドもそれぞれ追加する必要はあるな、「承認可能」「修正可能」「却下可能」「取下げ可能」「送付可能」…。で、状態に対応したStateオブジェクトを作って…と、この辺りで面倒臭さがいっぱいです。この程度でも面倒に感じて申し訳ない。

Rubyだと、特異メソッドを使うところなんだろかな。「Rubyデザインパターン」な情報は、設計思想も含めてJavaC++的なデザインパターンに引っ張られてるようなものしか見たことがないのが残念。何か面白い、ioを超えるようなやり方ってないんだろうか。