次!
Factory Method!
って、なにも「!」をつけなくてもいいのですが。『ペアになるオブジェクトを生成するメソッド(ハンドラ)』ですね。
あるオブジェクトとあるオブジェクトが密接な関係にあり、対で使う必要があるという状況。これらのオブジェクトを別々に生成し、利用しているとオブジェクトの数が増えるに従ってどれとどれが一緒に使う必要のあるオブジェクトか分からなくなってきます。それならば、対となるべきオブジェクトを生成するメソッドを自身に持たせておこう、という考え方のデザインパターンです。
例えば、Iterator の時に Stack の対となる StackIterator を作りました。以下に再掲(行数稼ぎにあらず)。
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
on StackIterator(theObject)
script StackIterator
property parent : Iterator(theObject)
on nextItem()
return pop() of my collectionObject
end nextItem
end script
end StackIterator
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
on getIterator()
return StackIterator(a reference to me)
end getIterator
end script
end Stack
on run
set dataList to Stack()
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
Stack の getIterator() を使うことで一緒に利用するオブジェクトを返してくれます(そのオブジェクトがどんなものかは使う方には分からない)。これを getIterator() を使わないように書き直してみると以下のようになります。
on run
set dataList to Stack()
tell dataList
push(10)
push(20)
push(30)
push(40)
end tell
set theIterator to StackIterator(dataList) -- *
theIterator's resetIndex()
repeat while hasNext() of theIterator
set thisItem to nextItem() of theIterator
log thisItem
end repeat
end run
コメントしている部分が書き直した部分です。getIterator() を使わない場合、ここが問題となります。Stack ではなく他のデータ構造を使おうとしたとき、この部分の修正も行わなければならないのです。それぐらいのこと、と思われるかもしれませんが複数のファイルにスクリプトを分割していると全て修正しないといけないとなれば面倒です。
getIterator() という Iterator オブジェクトを作って返すメソッドを使っている限り、利用する集合オブジェクトを変更しても修正は必要なくなります。
このように一緒に使って欲しい、対となるオブジェクトを生成するメソッド(ハンドラ)をオブジェクトの中に持たせるというのが Factory Method パターンです。Factory Method パターンの各オブジェクトの関係は、次のようになります。
Product(生成物) と Creator(創作者) があります。Product が対になるオブジェクトで Creator が Product を生成するハンドラを持っています。先の Iterator で言えば、StackIterator が Product にあたり、Stack が Creator になります。Creator は、Product を返す factoryMethod を実装します。getIterator() が factoryMethod になります。
この Factory Method パターンをシンプルにしたものに Factory パターンというものがあります。以前、『問題』というコンテンツを書いたとき、提示した問題に対して掲示板の方でお答えを頂きました。そのときの答えが Factory パターンでした。以下に再掲します(再掲ばっかり)。
script QuoEdit
on selectedText()
tell application "QuoEdit"
text of selection
end tell
end selectedText
end script
script CotEditor
on selectedText()
tell application "CotEditor"
contents of selection
end tell
end selectedText
end script
script KEdit
on selectedText()
tell application "KEdit"
selected text of front document
end tell
end selectedText
end script
on getEnhanceEditor()
tell application "System Events"
set apps to ((name of processes whose frontmost is true) as Unicode text)
end tell
if apps is "QuoEdit" then
return QuoEdit
else if apps is "CotEditor" then
return CotEditor
else if apps is "KEdit" then
return KEdit
else
return ""
end if
end getEnhanceEditor
on run
set EnhanceEditor to getEnhanceEditor()
display dialog (EnhanceEditor's selectedText())
end run
『KEdit、QuoEdit、CotEditor のいずれが最前面にあっても選択している文字列は、同じ方法で取得したい』というのが問題でした。上記のスクリプトではいずれのエディタであっても適切なオブジェクトを返し、利用者はそれがどのオブジェクトかを気にせずに選択している文字列が取得できるようになっています。
getEnhancedEditor() がオブジェクトを生成(してはいないけど)する工場の役割を担っています。Factory パターンでは、工場の中でどのオブジェクトを返すかを動的に判断します。どのオブジェクトを返すかという処理も利用者から隠しています。Factory Method よりも Factory パターンの方が実装が楽なのでよく利用されるようです。
しかし、この方法ではオブジェクトの数が増えるに従って if 文の条件判断が増えていき、オブジェクトの精製処理が複雑になるに従って工場内での手続きが煩雑になります。Factory パターンの考え方をさらに進めた Factory Method パターンは、これらの欠点をある程度やわらげてくれます。
しかし、抽象クラスやインターフェース、クラスメソッドや final を使えない AppleScript ではいまいち説明がしにくい...。Factory Method で説明のしにくさを感じるのですから、残りのデザインパターンではどうなることなのやら。これらが使えないと「なぜ、そうするのか」ということが非常に分かりにくいですね。興味のある方は『増補改訂版Java言語で学ぶデザインパターン入門』や『Javaデザインパターン徹底攻略 (標準プログラマーズライブラリ)』や『オブジェクト指向における再利用のためのデザインパターン』を読んでみるといいかもしれません。
0 件のコメント :
コメントを投稿