AppleScript とデザインパターン (7)

掲示板。そう、掲示板です。なんだか、いたずらされていますね。対処をしたいとは思うのですが、この掲示板はプロバイダが提供しているものなので対処のしようがなかったり。まぁ、基本的にはほっておきます。あまりにも続くようなら、こういう輩を相手に時間を潰すのもなんですので、あっさりと掲示板を閉じます。手軽ではないですが、なにかあればメールの方でご連絡を。GMail の blackcharan さん宛に。今後、メールも GMail に統一させようと思っています。PHP の勉強がてらに掲示板を作るという手もあるな。そうすれば、嫌いな人...もとい、嫌がらせをする人のフィルタリングも思いのまま。

以上、業務連絡でした。

Database Events は終わって、デザインパターンに戻ります。デザインパターンの話って今年中に終わるのかしらん。今回は、Template Method を取り上げます。。デザインパターンの中では比較的単純なパターンだそうです。

Template Method は、『決まった処理手順をテンプレートにし、処理手順の一部だけを置き換えやすくする』デザインパターンです。既存のハンドラを組み合わせて定型的な処理を行う AppleScript にはある意味お似合いのデザインパターンかな。

例えば、Finder で選択している項目に対してなんらかの処理を施したいというスクリプト。画像ファイルを回転したい。画像ファイルを縮小したい。画像ファイルのフォーマットを変更したい...。以下のような感じでしょうか?

Script Editor で開く

property extensionList : {"jpg", "jpeg", "tif", "tiff", "png"}

tell application "Finder"
    set curSelection to selection
    if curSelection is {} then return

    set imageFiles to {}
    repeat with thisItem in curSelection
        if (class of thisItem) is document file and (name extension of thisItem) is in extensionList then
            set end of imageFiles to thisItem as alias
        end if
    end repeat

    my shrinkingImages(imageFiles, 0.8)
    beep 2
end tell

on shrinkingImages(imageFiles, percentage)
    tell application "Image Events"
        launch
        repeat with thisImage in imageFiles
            try
                set imageRef to open thisImage
                scale imageRef by factor percentage
                save imageRef
                close imageRef
            on error
                try
                    close imageRef
                end try
            end try
        end repeat
    end tell
end shrinkingImages

Finder で選択している画像に対して縮小、拡大を行います。上書き保存するので大事な画像に使ってはいけません。では、画像をトリミングするスクリプトを。

Script Editor で開く

property extensionList : {"jpg", "jpeg", "tif", "tiff", "png"}

tell application "Finder"
    set curSelection to selection
    if curSelection is {} then return

    set imageFiles to {}
    repeat with thisItem in curSelection
        if (class of thisItem) is document file and (name extension of thisItem) is in extensionList then
            set end of imageFiles to thisItem as alias
        end if
    end repeat

    my rotateImages(imageFiles, -90) -- *
    beep 2
end tell

on rotateImages(imageFiles, degree)
    tell application "Image Events"
        launch
        repeat with thisImage in imageFiles
            try
                set imageRef to open thisImage
                rotate imageRef to angle degree
                save imageRef
                close imageRef
            on error
                try
                    close imageRef
                end try
            end try
        end repeat
    end tell
end rotateImages

画像を処理するハンドラが変更されました。Finder で処理をしている部分は同じものを使い回しています。実際はもう少し使いやすいように処理した画像を別名で保存したりするのですが、今回はデザインパターンを理解するためのものなので手を抜いています。

AppleScript は、上記のような決まった手順の処理というのが多いです。処理内容は同じで対象アプリケーションが変わっただけとか。こういうときに Template Method パターンを使えばしあわせになれるかもしれません。Template Method パターンでは、まず処理手順のテンプレートを作ります。処理手順のテンプレートというのは、ハンドラ呼び出しの組み合わせです。テンプレートは抽象クラス(AbstractClass。AppleScript にはクラスの概念がないですが、抽象オブジェクトというのもなんなので...)で作り、処理手順の一部を定義します。処理手順の一部というのはハンドラですね。

Template Method では処理のテンプレートは、親オブジェクトで定義し、変更されません。子オブジェクトで変更するのは、個々の処理だけです。処理手順は全ての子オブジェクトで使い回されます。テンプレートたる所以ですね。

上記のスクリプトでは、ファイルの選別とファイルの加工という処理があり、選別、加工と処理手順の流れがあります。

Script Editor で開く

script FileProcessor
    property extensionList : {}

    on selectItems()
        tell application "Finder"
            set curSelection to selection
            if curSelection is {} then return

            set theList to {}
            repeat with thisItem in curSelection
                if (class of thisItem) is document file and (name extension of thisItem) is in extensionList then
                    set end of theList to thisItem as alias
                end if
            end repeat
            return theList
        end tell
    end selectItems

    on processItems(theseItems)
    end processItems

    on run
        set theseItems to my selectItems()
        my processItems(theseItems)
    end run
end script

このような抽象的なオブジェクトを作ります。run ハンドラを templateMethod にしています。templateMethod では、自身で定義されている個別の処理を順番に呼び出します。selectItems() と processItems() が個別の処理になります。前者がファイルの選別を行い、後者で加工を行います。これらの個別の処理はこのスクリプトオブジェクトを継承したスクリプトオブジェクトで実装します。そうすることによって処理の一部だけを変更することが可能になります。

selectItems() ハンドラは既に実装しています。こういう変わらない処理なら、抽象クラスの方で実装しておく方がより楽になります。気に入らなければ継承先で上書きすればいいだけなので。以下のようにこのスクリプトオブジェクトを継承して使います。

Script Editor で開く

script ShrinkingImagesProcessor
    property parent : FileProcessor
    property extensionList : {"jpg", "jpeg", "tif", "tiff", "png"}
    property percentage : 0.5

    on processItems(theseItems)
        if theseItems is {} then return

        tell application "Image Events"
            launch
            repeat with thisImage in theseItems
                try
                    set imageRef to open thisImage
                    scale imageRef by factor percentage of me
                    save imageRef
                    close imageRef
                on error
                    try
                        close imageRef
                    end try
                end try
            end repeat
        end tell
    end processItems

    on setPercentage(num)
        set percentage of me to num
    end setPercentage

    on getPercentage()
        return percentage of me
    end getPercentage
end script

tell ShrinkingImagesProcessor to run

画像の回転も同じように継承を行い変更したい処理手順を上書きするだけです。

Script Editor で開く

script RotateImagesProcessor
    property parent : FileProcessor
    property extensionList : {"jpg", "jpeg", "tif", "tiff", "png"}
    property degree : 90

    on processItems(theseItems)
        if theseItems is {} then return

        tell application "Image Events"
            launch
            repeat with thisImage in theseItems
                try
                    set imageRef to open thisImage
                    rotate imageRef to angle degree of me
                    save imageRef
                    close imageRef
                on error
                    try
                        close imageRef
                    end try
                end try
            end repeat
        end tell
    end processItems

    on setDegree(num)
        set degree of me to num
    end setDegree

    on getDegree()
        return degree of me
    end getDegree
end script

tell RotateImagesProcessor to run

Template Method パターンを用いれば、既にある処理は使い回すことができます。また、各手順は個別に作成しているので修正も容易になります。以前に『どうせなら』という記事で書いた Livedoor Reader のログインスクリプトなどに Template Method を適用すれば、mixi や Gmail などのログインスクリプトも差分だけを書くことで流用することができます。

もちろん、問題がないわけでもありません。Template Method の問題点は、継承の継承を行っていくことでどのオブジェクトのどのハンドラが呼び出されているかが分かりにくくなるということです。以下のような感じです。

Script Editor で開く

script FileProcessor
    property extensionList : {}

    on selectItems()
        tell application "Finder"
            set curSelection to selection
            if curSelection is {} then return

            set theList to {}
            repeat with thisItem in curSelection
                if (class of thisItem) is document file and (name extension of thisItem) is in extensionList of me then
                    set end of theList to thisItem as alias
                end if
            end repeat
            return theList
        end tell
    end selectItems

    on processItems(theseItems)
    end processItems

    on run
        set theseItems to my selectItems()
        my processItems(theseItems)
    end run
end script

script ScriptFileProcessor
    property parent : FileProcessor
    property extensionList : {"scpt", "scptd", "applescript"}

    on processItems(theseItems)
        repeat with thisItem in theseItems
            tell application "Finder" to open thisItem
        end repeat
    end processItems
end script

script JPEGFileProcessor
    property parent : FileProcessor
    property extensionList : {"jpg", "jpeg"}

    on processItems(theseItems)
        if theseItems is {} then return

        tell application "Image Events"
            launch
            repeat with thisImage in theseItems
                try
                    set imageRef to open thisImage
                    rotate imageRef to angle 90
                    save imageRef
                    close imageRef
                on error
                    try
                        close imageRef
                    end try
                end try
            end repeat
        end tell
    end processItems
end script

script TIFFFileProcessor
    property parent : JPEGFileProcessor
    property extensionList : {"tif", "tiff"}
end script

tell TIFFFileProcessor to run

最後の TIFFFileProcessor が FileProcessor を継承している JPEGFileProcessor を継承しています。ここでは個別の処理をそれぞれで定義していませんが、こういう継承を行っているとどのオブジェクトのどのハンドラを呼び出しているのかということが分かりにくくなってきます。バグが見つけにくい、と。あまり深い継承は行わない方がいいようです。

Template Method パターンは、Factory / Factory Method パターンと深い関係があるそうで。オブジェクトの生成処理に Template Method パターンを適用することができますし、Template Method パターンで作ったオブジェクトのどれを利用するかを Factory を使って動的に変更することが可能になります。

デザインパターンは一つだけでなく複数を組み合わせることでさらに威力を発揮するということですね。

0 件のコメント :

コメントを投稿