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

次!

Factory Method!

って、なにも「!」をつけなくてもいいのですが。『ペアになるオブジェクトを生成するメソッド(ハンドラ)』ですね。

あるオブジェクトとあるオブジェクトが密接な関係にあり、対で使う必要があるという状況。これらのオブジェクトを別々に生成し、利用しているとオブジェクトの数が増えるに従ってどれとどれが一緒に使う必要のあるオブジェクトか分からなくなってきます。それならば、対となるべきオブジェクトを生成するメソッドを自身に持たせておこう、という考え方のデザインパターンです。

例えば、Iterator の時に Stack の対となる StackIterator を作りました。以下に再掲(行数稼ぎにあらず)。

Script Editor で開く

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() を使わないように書き直してみると以下のようになります。

Script Editor で開く

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 Editor で開く

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 件のコメント :

コメントを投稿