Factory Method の続きです。前回の最後の方に Factory(SimpleFactory) の例として以前に掲示板の方でご指摘いただいたコードを掲載しました。そのコードを Factory Method で書き直すとどうなるのでしょうか?
これがなかなか頭が痛い問題でして...。Factory Method で書き直してはいたのです。が、掲載を見送ったのでした。納得できなくて(というか、よく分からなくて)。とりあえず、もう一度、Factory Method パターンについて。
Factory Method パターンは、オブジェクトの生成を直接に行わず、オブジェクトの生成処理とそのオブジェクトを使用するものとの分離を行い、再利用性を高めます。AppleScript ではオブジェクトの生成というとハンドラ呼び出しがそれに当たります(もしくは、load script 命令による読み込み。copy 命令は...複製ですよね)。例えば、以下のような感じです。
on TextEdit()
script TextEdit
on selectedText()
tell application "TextEdit"
if not (documents exists) then return ""
activate
end tell
set tmp to the clipboard
tell application "System Events"
tell process "textedit"
keystroke "c" using command down
end tell
end tell
if (the clipboard) is tmp then return ""
return the clipboard as Unicode text
end selectedText
end script
end TextEdit
on KEdit()
script KEdit
on selectedText()
tell application "KEdit"
selected text of front document
end tell
end selectedText
end script
end KEdit
-- client
on run
set theEditor to TextEdit()
tell theEditor to selectedText()
end run
使用者(run ハンドラ)は、オブジェクトの生成を直接行っています。この場合に困るのは生成するオブジェクトを変更した時。使用者はこの部分を変更しなくてはいけません。生成処理が変更されたら使用者にも影響が出ます。オブジェクトを使う方としてはこのような影響は受けたくありません。そこで、生成の処理をハンドラにしてしまいます。
script SimpleFactory
on getEditor()
tell application "System Events"
set a to ((name of processes whose frontmost is true) as Unicode text)
end tell
if a is "TextEdit" then
return TextEdit()
else if a is "KEdit" then
return KEdit()
else
return missing value
end if
end getEditor
end script
-- client
on run
tell application "TextEdit" to activate
set theEditor to SimpleFactory's getEditor()
if theEditor is missing value then return missing value
tell theEditor to selectedText()
end run
こうしておくと生成処理に変更が加えられても使用者は気にすることなく利用できます。これが、Factory パターン(SimpleFactory ともいう)。これだけでもかなりの恩恵があります。では、Factory と Factory Method の違いは?
Factory は、オブジェクトの生成処理とどのオブジェクトを生成するかという判断も使用者から隠します。Factory Method パターンでは生成するオブジェクトごとに工場を用意し、ひとつの工場でひとつのオブジェクトを生成します。生成されるオブジェクトがどんなオブジェクトかということは使用者からは隠されますが、どのオブジェクトを生成するかという判断は使用者に委ねられます。生成するオブジェクトを変更したい場合、工場を持っているオブジェクトを切り替える必要があります。この辺りはできれば実行時に判断してオブジェクトを変更して欲しいものです。そのため、なんらかの仕掛けが必要になります。例えば、文字列からオブジェクトを生成するとか。
単純だけど分かりやすい SimpleFactory を使った方が無理がありません。この辺りは、ケースバイケース。Factory Method パターンは、大量のオブジェクトを生成する必要があるときや一緒に使って欲しいオブジェクトがある時などに利用できるパターンだそうで、あるオブジェクトとあるオブジェクトを一緒に使うということがはっきり分かっている時以外は適用しない方がいい、との注意もあります。それぞれのオブジェクトが単独で使えるような場合、なんでそのオブジェクトでオブジェクトを生成しているのかということが分かりにくくなるからです。
もしかしたら、Factory Method ではなく、Abstract Factory パターンの方がしっくりくるのかもしれません。まぁ、それは Abstract Factory の時のお話。では、引っ張りましたが Factory Method パターンを使って書き直したスクリプトを。
まず、生成されるオブジェクトをスクリプトファイルで保存します。
on TextEdit()
script TextEdit
on selectedText()
tell application "TextEdit"
if not (documents exists) then return ""
activate
end tell
set the clipboard to ""
tell application "System Events"
tell process "textedit"
keystroke "c" using command down
end tell
end tell
return the clipboard as Unicode text
end selectedText
end script
end TextEdit
on init()
return TextEdit()
end init
これを TextEdit.scpt として保存。init() は必ず実装します。以下のスクリプトを KEdit.scpt として保存。
on KEdit()
script KEdit
on selectedText()
tell application "KEdit"
selected text of front document
end tell
end selectedText
end script
end KEdit
on init()
return KEdit(application "KEdit")
end init
オブジェクトを生成する工場を持つ以下のスクリプトを保存。
on EditorEx(theEditor)
script EditorEx
property editor : theEditor
on selectedText()
set theEditor to my editorFacorty()
if theEditor is missing value then return missing value
tell theEditor to selectedText()
end selectedText
on editorFacorty()
set appName to short name of (info for (path to editor))
set fileName to appName & ".scpt"
set scptFolder to path to scripts folder from user domain as Unicode text
set scptFolder to scptFolder & "Applications:ExtraEditor:"
set scptFile to scptFolder & fileName as Unicode text
try
return init() of (load script file scptFile)
on error eMessage number eNumber
return missing value
end try
end editorFacorty
end script
end EditorEx
on init(theEditor)
EditorEx(theEditor)
end init
ここでは、Editor.scpt という名前で保存しました。これらのファイルを ~/Library/Scripts/Applications/ に ExtraEditor というフォルダを作り、この中に保存します(もちろん、どこでもいいのですが)。Applications の中に保存するのは、スクリプトメニューに表示されないからです(ExtraEditor というアプリケーションがない限り)。
使用者は以下のような感じで呼び出します。
on run
tell application "System Events" to set f to name of (path to frontmost application)
set scptFolder to path to scripts folder from user domain as Unicode text
set scptFolder to scptFolder & "Applications:ExtraEditor:"
set scptFile to scptFolder & "Editor.scpt"
-- 初期化
set theEditor to init(application f) of (load script file scptFile)
-- 選択テキストを取得
set str to selectedText() of theEditor
if str is missing value then return
display dialog str
end run
対象のエディタを指定して初期化します。ここでは、前面にあるアプリケーションの名前で初期化しています。前面のアプリケーションが対象のエディタでなかった場合、missing value が返ってきます。スクリプトメニューなどから実行してみてください。あとは、勝手にオブジェクトが判断するのでらくちん。バンドル形式にしてバンドルの中に全部のスクリプトを入れても構いません(その場合、修正が必要です)。
これなら対応エディタの数が増えても使用者の方で変更はいらないですね、と作った後に気がつく。オブジェクトを作成する方も変更がいらないですし。ひとつの工場がひとつのオブジェクトを作成するという部分もあっていますし、と自画自賛。いや、もうここまで作るのにどれだけの無駄スクリプトを書いたことか。累々たるってところです。ちなみに、今回掲載したスクリプトは、最後の最後で全面的に変更したものです。以前のものは...見れたものではなかったり。
ところで、short name って AppleScript 1.10 からでしたっけ?なら、Mac OS 10.4 以前では動かないですね。
若干、強引な感じもなきにしもあらずですが...こんなところでいかがでしょうか?
追記 - 06/09/29
掲示板の方でご指摘をいただきました。これ、よくよく見ると editorFacorty() ハンドラだけでいいですね。パターンにとらわれ過ぎていました。自戒の意味も込めて修正せずにこのままにしておきます。
0 件のコメント :
コメントを投稿