今回は、『オブジェクト指向における再利用のためのデザインパターン』の中から State パターンを取り上げます。以前に Strategy パターンを取り扱ったことがありますが、State パターンは Strategy パターンと同じクラス図、同じ実装方法になるデザインパターンです。
状態に応じて処理を変更する...例えば、iTunes の再生/一時停止のボタン。これは、一つのボタンですが、再生中に押すと一時停止になり、一時停止中に押すと再生になります。一つのボタンで複数の状態が表現されています。このようなボタンをプログラムで書くとなると...まずは、以下のようなコードを思いつくと思います。
property playing : false
if playing then
stop()
set playing to false
else
play()
set playing to true
end if
条件分岐による状態の変更ですね。再生と一時停止だけなのでそれほど複雑になるわけではないですが、時間によって異なる挨拶を返すようなプログラムでは、「おはよう」、「こんにちは」、「こんばんは」の 3 つが必要になります。返す挨拶が増えると分岐も増えます。
State パターンでは状態をオブジェクトで表現し、状態オブジェクトを交換することで異なった処理を行います。具体的には if 文で分岐しているそれぞれの処理を状態オブジェクトととして分割します。
script Morning
on showMessage()
return "おはよう"
end showMessage
end script
script Daytime
on showMessage()
return "こんにちは"
end showMessage
end script
script Night
on showMessage()
return "こんばんは"
end showMessage
end script
朝、昼、晩の挨拶をオブジェクトにします。これらを交換することで、それぞれの状態に対応します。
script Greeting
property currentState : Morning
on setState(theObject)
set currentState of me to theObject
end setState
on showMessage()
currentState's showMessage()
end showMessage
end script
on run
tell Greeting
showMessage()
--> "おはよう"
setState(Daytime)
showMessage()
--> "こんにちは"
setState(Night)
showMessage()
--> "こんばんは"
end tell
end run
このような感じです。...あれ?サンプルのコードが変わっただけで Strategy パターンと同じでは...?
全くその通りで...どうしましょう。えーっと『Strategy パターンはアルゴリズムの交換を目的とし、State パターンは状態を動的に変更したいというときに使うもので、Strategy パターンとは概念が違います』。...え、違いってそれだけ?
これで、State パターンが終わったら何とも釈然としない。ちゃんと、Strategy パターンとの際を明確にしましょう。まず、State パターンは、状態を表すオブジェクトが存在します。これ(ら)を State オブジェクトといいます。そして、State オブジェクトを保持し、現在の状態を表すとともに状態の動作を呼ぶ出す役目を持つ Context オブジェクトが存在します。利用者はこの Context オブジェクトにアクセスします。
上記の挨拶でいえば、Morning、Daytime、Night が State オブジェクトにあたり、Greeting が Context にあたります。利用者(run ハンドラ)は、Greeting の showMessage() を呼び出します。Greeting は、自分が保持している State オブジェクトによって適切な挨拶を返します。
State パターンは、このように状態によって振る舞いが変化し、状態の遷移(挨拶は時間が経つと変わる)を表現するときに利用するといいデザインパターンです。
iTunes は playpause という命令を持っています。再生中のときは一時停止、一時停止のときは再生になります。この命令を模してみます。再生中の状態と一時停止中の状態。まず、この 2 つの状態オブジェクトを作ります。
on Playing()
script
on action(theObject)
tell application "iTunes" to pause
tell theObject to setState(pausedObject)
end action
end script
end Playing
on Paused()
script
on action(theObject)
tell application "iTunes" to play
tell theObject to setState(playingObject)
end action
end script
end Paused
それぞれ、action() ハンドラを持っていて、Playing の時は iTunes で一時停止を行います。Paused のときは、再生を行います。そして、次の状態として他方を登録するようにします。
状態を保持する Context オブジェクトの役割を担う Player というオブジェクトを追加します。
on Player(stateObject)
script
property currentState : stateObject
on setState(theObject)
set currentState of me to theObject
end setState
on playpause()
tell currentState of me to action(me)
end playpause
end script
end Player
状態を登録する setState() というハンドラと playpause() というハンドラがあります。playpause() ハンドラでは登録されている状態の action() ハンドラを自分を引数にして呼び出します。
これらを利用する側は以下のようなコードを記述します。
property playingObject : missing value
property pausedObject : missing value
property playerObject : missing value
on run
initialize()
playerObject's playpause()
end run
on reopen
playerObject's playpause()
end reopen
on idle
tell application "iTunes"
set theState to player state
if theState is paused or theState is stopped then
playerObject's setState(pausedObject)
else if theState is playing then
playerObject's setState(playingObject)
end if
end tell
return 1
end idle
on initialize()
set playingObject to Playing()
set pausedObject to Paused()
tell application "iTunes"
set theState to player state
if theState is paused or theState is stopped then
set playerObject to my Player(pausedObject)
else if theState is playing then
set playerObject to my Player(playingObject)
else
set playerObject to my Player(pausedObject)
end if
end tell
end initialize
最初に iTunes の現在の状態を調べて Player オブジェクトを作っています。あとは、Player オブジェクトの playpause() ハンドラを呼び出せば、iTunes の playpause と同じ動作をします。idle ハンドラでは iTunes の状態を調べてオブジェクトを再登録しなおしています。
しかし、関係のないところでつまずきました。iTunes の player state 属性って Script Editor 上では as で文字列に変換できるのですが、アプリケーションで保存したら変換できないのですね。おかげで if 文のどこにも引っかかりませんでした。今まで文字列に変換して使っていて大丈夫だから気がつかなかった...。まったく...なんで、エラーになるのかなとさんざん悩みました。
このサンプルでは状態を状態自身が次の状態として変更していますが、状態はどこで変更しても構いません。実際、idle ハンドラの中で変更しています。さて、このサンプルで Strategy パターンとの違いを分かっていただけたでしょうか?あまり自信がありませんが...。
0 件のコメント :
コメントを投稿