倭マン's BLOG

くだらない日々の日記書いてます。 たまにプログラミング関連の記事書いてます。 書いてます。

はじめての幻獣 Griffon 研 (30) : マルチ MVC への道 (7) : 込み入った MVC グループを生成する buildMVCGroup()

今回は込み入った MVC グループを生成する方法を見ていきます。 もう少し具体的には、アクションを View スクリプトから分離する方法を見ていきます(一覧)。 今回で「マルチ MVC への道」は終了(予定)。

この記事のタイトルと以下の内容だと少々誤解を招きそうなので注意しておくと、「アクションを分離する」のに必ずしも buildMVCGroup() メソッドを使う必要はありません。 buildMVCGroup() メソッドを使うサンプルを書きたかったので、この記事ではまとめて扱ってるだけです。

  • アクションの分離」に必要なのは以下の2つです:
    • Application.groovy での actions の定義
    • アクションを定義したスクリプト XxxxActions.groovy
  • buildMVCGroup() メソッドは MVC グループを新たに生成するメソッドで、ほとんど createMVCGroup() メソッドと同じです(違いは返り値)。

これらを踏まえて、「アクションの分離」を行う方法を見ていきましょう。

「MonolineFunction」 MVC グループのアクションを分離する


では「関数描画アプリケーション」で「MonolineFunction」 MVC グループのアクションを分離していきます。 変更を施すのは以下のソース:

  • FunctionPlotter/griffon-app/conf/Application.groovy
  • FunctionPlotter/griffon-app/actions/functionplotter/MonolineFunctionActions.groovy
  • FunctionPlotter/griffon-app/views/functionplotter/MonolineFunctionView.groovy (actions ノードの削除)
  • FunctionPlotter/griffon-app/controllers/functionPlotterController.groovy (必ずしも必要でない)

実質的には、「アクションの分離」に大事なのは上の2つだけです。

Application.groovy

まずは MVC グループのメンバとして「actions」を定義する必要があります。 これは「FunctionPlotter/griffon-app/conf」フォルダにある Application.groovy で設定します:

application { ... }

mvcGroups {
    // MVC Group for "MonolineFunction"
    'MonolineFunction' {
        model = 'functionplotter.MonolineFunctionModel'
        actions = 'functionplotter.MonolineFunctionActions'    // 追加
        controller = 'functionplotter.MonolineFunctionController'
        view = 'functionplotter.MonolineFunctionView'    // これより上に actions を定義
    }

    // MVC Group for "FunctionPlotter"
    'FunctionPlotter' { ... }
}

「actions」も、他のMVC グループのメンバと同じように「《ロール名》='《クラス名》'」の形で定義します。 注意が必要なのは、actions の定義は view の定義より上に追加する必要があることでしょうか。 View のインスタンス化より前に、そこで参照されるアクションがインスタンス化されていなければいけないようです(各メンバを上から順にインスタンス化するもよう)*1

MonolineFunctionActions.groovy

次はアクションを定義するスクリプトを書きます。 ファイルは MonolineFunctionActions.groovy とし、「FunctionPlotter/griffon-app/actions/functionplotter」フォルダに配置します*2

Actions には View に定義していたアクションを定義します。

package functionplotter

action(id:'remove', name:'Remove', closure:controller.remove)

action ノードの書き方は View のものと同じです。 ただし、actions ノードは書かないことに注意*3

MonolineFunctionView.groovy

View にはアクションを定義する必要がなくなったので、View からは actions ノードを削除します。

FunctionPlotterController.groovy

Controller のコード変更は特に必須ではありませんが、生成された MVC グループ のインスタンスをあれこれいじる場合は、createMVCGroup() の返り値(Model, View, Controller オブジェクトのリスト)よりも buildMVCGroup() の返り値(MVC グループのメンバ・オブジェクトの Map)を使った方が読むのも書くのも簡単です。

package functionplotter

class FunctionPlotterController {

    def model
    def view

    def functionId = 'f'
    private List functionModels = [] as LinkedList

    void mvcGroupInit(Map args) {
        buildFunctionControl()
    }

    def addFunction = { evt = null ->
        def group = buildFunctionControl()
        this.view.with{
            functionPanel.add(group.view.content)
            controlPanel.updateUI()
        }
    }

    private buildFunctionControl(String groupName = 'MonolineFunction'){
        def group = buildMVCGroup(groupName, this.functionId, name:this.functionId+'(x)')
        this.functionModels << group.model
        this.functionId++
        return group
    }

    ...
}

以前は createFunctionControl だったメソッド名を buildFunctionControll に変更しているのは大して意味はありません。

もしコード中で、生成された MonolineFunctionActions オブジェクトにアクセスしたい場合は、他の MVC グループのメンバと同様に「actions フィールド」を定義すれば、自動でオブジェクトを注入してくれます:

class FunctionPlotterController {

    def model
    def view
    def actions    // 生成された MonolineFunctionActions オブジェクトが自動でセットされる

    def someMethod(){
        def removeAction = actions.remove
        ...
    }
}

他にも以下のようなアクセス方法も可能です:

class FunctionPlotterController {

    def model
    def view
    def mvcName    // この MVC グループの groupId (自動でセットされる)

    def someMethod(){
        def removeAction =  app.groups[mvcName].actions.remove
        ...
    }
}

要は Application.groovy で MVC グループのメンバを定義すれば、Model, View, Controller と同じようにそのグループのメンバとして扱えるということですね。

buildMVCGroup() メソッド


最後に buildMVCGroup() メソッドの API についてまとめておきます。 といっても、以前に見た createMVCGroup() メソッドとほとんど同じです*4。 (メソッド名以外の)唯一の違いは、メソッドの返り値が MVC グループのメンバ・オブジェクトの Map だという点です。

まずは buildMVCGroup() メソッドのオーバーロード。

buildMVCGroup(String groupName)
buildMVCGroup(String groupName, String groupId)
buildMVCGroup(String groupName, Map params)
buildMVCGroup(String groupName, String groupId, Map params)

シグニチャをまとめると下表の通り:

名前 必須 説明
groupName String Yes MVC グループの名前。 Application.groovy に登録されている名前。
groupId String No グループ ID。 MVCインスタンスにアクセスする際に指定するキーとして使う。
params Map No MVC インスタンスの初期化に使用するパラメータ。

createMVCGroup() メソッドとの違いである返り値は、使い方を書いた方が分かりやすいかと:

def group = buildMVCGroup('MonolineFunction', 'f', name:'f(x)')

assert group.model     instanceof functionplotter.MonolineFunctionModel
assert group.view        instanceof functionplotter.MonolineFunctionView
assert group.controller instanceof functionplotter.MonolineFunctionController
assert group.actions    instanceof functionplotter.MonolineFunctionActions

Map のキーとして使用されているのは、Application.groovy で指定されたキーです。

ちなみに createMVCGroup() メソッドでは、Model, View, Controller オブジェクトのリストが返されるのでした。 イメージとしてはこんな感じ:

def createMVCGroup(String groupName, String groupId = groupName, Map params = [:]){
    def group = buildMVCGroup(groupName, groupId, params)
    return [model:group.model, view:group.view, controller:group.controller]
}

実際にこういう実装になってるわけではありませんが。

これでとりあえず「マルチ MVC への道」は一段落。

Griffon in Action

Griffon in Action

*1:Actions で参照される Controller のプロパティがあっても、actions を controller より先にインスタンス化して例外が発生しないのがナゾ。 スクリプトかどうかで扱いが違うようではあるけど、あまり気にするほどのものでもないかな。

*2:Application.groovy での設定を変えれば必ずしもファイル名(クラス名)を 「MonolineFunctionActions.groovy」にする必要はありません。 また、パッケージ階層にあっていれば「actions」フォルダに配置する必要もないようです。 ただし、このようにするとコードの保守が面倒になるだけなので、これについてこれ以上は触れません。

*3:拙者の環境では例外が出ました。

*4:実際、createMVCGroup() メソッドは buildMVCGroup() メソッド(で呼ばれる処理)を呼び、その返り値を変更しているだけのようです。