yucatio@システムエンジニア

趣味で作ったものいろいろ

Groovyのクロージャ ②クロージャのthisとownerとdelegate

背景

JenkinsというCIツールではGroovyのコードでCI挙動を記述できるJenkins pipelineがあります。 Jenkins pipelineではGroovyのDSL(Domain-Specific Language:ドメイン固有言語)が使われています。 DSLにはクロージャが効果的に使用されています。

今回はクロージャのthisとownerとdelegateです。

前回の記事

前回の記事で、 クロージャとは生まれ故郷(クロージャ自身が定義された場所)を忘れない無名関数のこと、 クロージャは、クロージャが定義された場所で動く(ように見える)、ということが確認できました。

yucatio.hatenablog.com

クロージャが故郷を忘れないためにしていること

クロージャが実行されたときどのようなことが起こっているのでしょうか。どのようにして元のクラスのメソッドを呼ぶのでしょうか。

その答えは、クロージャが故郷(クロージャが定義された場所)の情報を持っているからです。

クロージャはオブジェクトなので、内部に情報を持てます。クロージャインスタンス変数にはthisownerdelegateの3つが定義されています。

それぞれ以下の情報が入っています。

フィールド 入ってる情報
this クロージャを囲んでいるクラス
owner クロージャを囲んでいるオブジェクト。クラスか、クロージャを囲んでいるクロージャ
delegate 外部のオブジェクト。メソッドやプロパティ名が元のオブジェクトで解決できないときに呼ばれる

確認してみましょう。

class Main {
    // 普通のインスタンスメソッド
    def closureTest() {
        Closure cl = {
          a()
        }
        println "this     : ${cl.thisObject}"
        println "owner    : ${cl.owner}"
        println "delegate : ${cl.delegate}"
    }
    
    def a() {
        println "I'm Main.a"
    }
}

def main = new Main()
main.closureTest()

実行結果です。

this     : Main@2e654c59
owner    : Main@2e654c59
delegate : Main@2e654c59

すべてMainクラスのインスタンスという結果でした。クロージャの中にさらにクロージャがある場合(クロージャがネストしている場合)にはownerとdelegateの値が、対象のクロージャを囲むクロージャになります。詳しくはこちらを参照してください。

qiita.com

ここまでで、クロージャは故郷を忘れないためにthisownerに生まれた場所の情報を格納していることが分かりました。 delegateは、デフォルトではownerと同じですが、クロージャが名前解決に使用するオブジェクトを格納するために使用されます。

delegateにクラスを指定する

delegateにクラスを指定します(正確にいうと、クラスのインスタンスを指定します)。Pipelineクラスのpipeline()メソッド内で、クロージャdelegateを変更します。 PipelineSpecクラスにa()メソッドを定義しました。Mainクラスにはa()メソッドはありません。

class PipelineSpec {
    def a() {
      println "I'm PipelineSpec.a"
    }
}

class Pipeline {
    def pipeline(Closure cl) {
      cl.delegate = new PipelineSpec()
      println "before closure"
      cl()
      println "after closure"
    }
}

class Main {
    def closureTest() {
        def pipeline = new Pipeline()
        pipeline.pipeline {
            a()
        }
    }
}

def main = new Main()
main.closureTest()

実行結果です。

before closure
I'm PipelineSpec.a
after closure

クロージャcl内のa()メソッドは、PipelineSpecのa()メソッドを呼んでいます。 このように、ownerのオブジェクトに該当するメソッドがない場合は、delegateに指定されたオブジェクトのメソッドを実行します。

環境

参考リンク

次回に続く

yucatio.hatenablog.com