Iterator。
微妙。AppleScript にとって Iterator パターンって微妙。そもそも必要なのでしょうか。AppleScript は、大概の場合処理するデータをアプリケーションが持っています。そして、それらを取得して繰り返しで処理を行います。
tell application "Finder"
set curSelection to selection
if curSelection is {} then return
repeat with thisItem in curSelection
if (class of thisItem is document file) and (name extension of thisItem is "scpt") then
open thisItem
end if
end repeat
end tell
こんな感じ。ある意味、型が決まっているともいえます。アプリケーションがデータを持っていてそれを処理する AppleScript では、自分でデータ構造を作るということがあまりないです。Iterator パターンは『様々な集合オブジェクトに同一のアクセス方法を提供する』デザインパターンです。ある人が独自のデータ構造を作ったとします。他の人がこのデータ構造を走査したいという時に「Iterator を使ってね」と言われれば、そのデータ構造がどんなものか分からなくてもデータを走査することができるようになります。
データ構造の中身が分からなくても Iterator を使えばデータの処理ができる、というのが Iterator パターンの目的なのですが、AppleScript でこういう状況に出会うことがない。スクリプトを分割したり、大規模な開発でも行えばそうではないのかもしれないですが(そもそも AppleScript でそんな開発も少ないし、分担で開発も少ない)。
ま、ともかく。以下のような疑似コードではイケナイようですから、AppleScript でも上記の repeat の書き方はイケナイのでしょう。
for (i = 0 ;i < SIZE ;i++) {
item = array[i];
}
これが駄目なのは、array の内部構造を意識しないといけないからですって。奥さん。
駄目と言われても...例えば、スクリプトを分割したとします。データを持っているスクリプトとそのデータを繰り返しで処理するスクリプト。データを持っている方のデータ構造を変更したら、繰り返しで処理する方も変更しないといけません。上記の疑似コードが通用するのはプログラムを分割しないときだけです。...ということのようです。だから、どんなデータ構造でも同じ方法でアクセスできる方法が必要になります、と。
前置きはこれぐらいにして。Iterator パターンでは、集合を表すオブジェクト(Aggregate = 集合、集合体)と集合の各要素に順番にアクセスするオブジェクト(Iterator = 繰り返し)が必要になります。集合を表すオブジェクトではなんらかのデータの集合を保持し、自身の各要素にアクセスするための Iterator オブジェクトを返すハンドラが実装されます。
Iterator オブジェクトでは、次の要素があるかどうかを返す hasNext() というハンドラと、次の要素を取り出す next() というハンドラが実装されます。
集合オブジェクトから Iterator オブジェクトを取得し、このオブジェクトの hasNext() で要素があるかどうかを調べ、next() で次の要素を取り出します。それがどのようなデータ構造でもこの手順でデータが取り出せるってスンポー。
まず、データの集合(Aggregate)となるオブジェクトを。
on DataStructure()
script DataStructure
property theCollection : {}
on pop()
end pop
on push(thisItem)
set end of my theCollection to thisItem
end push
on getLength()
return count my theCollection
end getLength
on getItemAt(num)
return item num of theCollection
end getItemAt
on getIterator()
return Iterator(a reference to me)
end getIterator
end script
end DataStructure
on Stack()
script Stack
property parent : DataStructure()
on pop()
if (my theCollection) is {} then return missing value
set tmp to last item of my theCollection
set my theCollection to reverse of (rest of (reverse of my theCollection))
return tmp
end pop
end script
end Stack
on Queue()
script Queue
property parent : DataStructure()
on pop()
if (my theCollection) is {} then return missing value
set tmp to first item of my theCollection
set my theCollection to rest of my theCollection
return tmp
end pop
end script
end Queue
スタックとキューで試してみます。どちらでも共通するハンドラを DataStructure で定義して Stack と Queue ではデータを取り出す部分だけを上書きしています。これらのデータ構造に順番にアクセスするための Iterator オブジェクトを返すハンドラは DataStructure で定義しています。
Iterator は、以下のようになります。
on Iterator(theObject)
script Iterator
property collectionObject : theObject
property itemIndex : 1
on hasNext()
return (my itemIndex) is less than or equal to (getLength() of my collectionObject)
end hasNext
on nextItem()
set num to itemIndex
set itemIndex to itemIndex + 1
return (getItemAt(num) of my collectionObject)
end nextItem
on resetIndex()
set itemIndex of me to 1
end resetIndex
end script
end Iterator
引数に集合オブジェクトをとります。集合オブジェクトに順番にアクセスするため、集合オブジェクトの現在の要素の位置を属性で持っています。AppleScript の集合の要素は 1 から始まるので、初期値は 1 です。hasNext() では、集合オブジェクトに次の要素があるかどうかを調べます。nextItem() で実際に次の要素を取り出します。
この両方のハンドラで集合オブジェクトの要素数や特定の位置の要素を取り出す必要があるので DataStructure ではその辺りのハンドラも実装しています。resetIndex() は、要素の開始位置を初期値に戻すハンドラです。
そして、以下のように使います。
on run
set dataList to Stack() -- or Queue()
tell dataList
push(10)
push(20)
push(30)
push(40)
end tell
set theIterator to getIterator() of dataList
theIterator's resetIndex()
repeat while hasNext() of theIterator
set thisItem to nextItem() of theIterator
log thisItem
end repeat
end run
--> 結果
(*10*)
(*20*)
(*30*)
(*40*)
キューであってもスタックであっても同じ方法でデータの最初から最後までを走査できています。
ええ、分かっています。スタックでデータが最初から表示されたらおかしいじゃないか、ということですね。Iterator は、全てのデータを走査するための方法を提供しているだけなので構わないと言えば構わないのですが。スタックのように最後に入れたものが最初に取り出されるようにするには、Iterator を継承して nextItem() を上書きし、Stack の方も getIterator() を上書きします。
on StackIterator(theObject)
script StackIterator
property parent : Iterator(theObject)
on nextItem()
return pop() of my collectionObject
end nextItem
end script
end StackIterator
on Stack()
script Stack
property parent : DataStructure()
on pop()
if (my theCollection) is {} then return missing value
set tmp to last item of my theCollection
set my theCollection to reverse of (rest of (reverse of my theCollection))
return tmp
end pop
on getIterator()
return StackIterator(a reference to me)
end getIterator
end script
end Stack
StackIterator の nextItem() で pop() を実行しています。Stack の方では getIterator() で StackIterator を返すようにしてます。実行部分は修正する必要がないのでこのままで。実行すると逆順で要素が返ってきます。
...で。
何だ、「で」って。いえ、もう少し使い道のあるサンプルでも作ろうかと思ったのですが...思いつきませんでした。メッセージキューなんかだったら AppleScript でも使い道あるかな。
そんなわけで...Iterator パターンでした。
0 件のコメント :
コメントを投稿