2021年11月21日日曜日

4: Gradleにてクラスを定義し使用する

<このシリーズの前の記事 | このシリーズの目次 |

Gradleスクリプト内に定義されたクラスは何になるのか?スクリプトクラス内のインナークラスか、'run'メソッド内のローカルインナークラスか、それとも他の何にか?

話題


About: Gradle

この記事の目次


開始コンテキスト



ターゲットコンテキスト



  • 読者は、Gradleにて定義されたクラスが何になるのか、およびクラスを定義し使用する方法を知る。

オリエンテーション


Gradleにおいてスクリプトを書く際のより良い様式を紹介する記事があります。


本体

ト書き
Special-Student-7が、日本の、いくつかの山に囲まれた、古い、かなり孤立した家の、ある部屋にいる。


1: スクリプト内にクラスを定義する


Special-Student-7-Hypothesizer
スクリプト内にクラスを定義できる、以下のように。

@Gradle ソースコード
class CustumTaskA extends DefaultTask {
	@TaskAction
	void action () {
		println ("### CustumTaskA working!")
	}
}

自分のクラスを定義する理由の1つは、カスタムタスククラスを定義しなければならないということだ、上記コードにおけるように。

別の理由は、複数のタスク内で使われるユーティリティクラスが欲しいことだ。


2: それは、インナークラスにもローカルインナークラスにもならない


Special-Student-7-Rebutter
いずれにせよ、そうしたクラスは何になるのか?つまり、そのクラスは、自動生成された'org.gradle.groovy.scripts.BasicScript'サブクラス内のどこに置かれるのか?

Special-Student-7-Hypothesizer
私が推測したのは、スクリプトクラス内のインナークラスになるか、もしくは、'run'メソッド内のローカルインナークラスになるかだったが、結局、そうではないことが分かった。

それは、アウタークラスになる。

Special-Student-7-Rebutter
うむ?その事実は何をもたらすのか?

Special-Student-7-Hypothesizer
そのアウタークラスは、スクリプトクラスフィールド群、スクリプトプロパティ群、プロジェクトプロパティ群に直接アクセスできない。

Special-Student-7-Rebutter
えーと、それは、かなり不便だ。スクリプトクラス内にインナークラスを定義できないのか?

Special-Student-7-Hypothesizer
私が知る限り、ノーだ。


3: スクリプトファイルの外にクラスを定義する


Special-Student-7-Hypothesizer
当該クラスはスクリプトクラスのインナークラスにならないので、私はあまりそれをスクリプトファイル内に書きたくない、なぜなら、そのクラスは、本当にはそのスクリプトの一部にならないから。

Groovy的には、そうしたクラスは別のGroovyソースファイル('groovy'のファイル名拡張子の)内に入れられるが、えーと、GradleはそのようなGroovy ソースファイルも'gradle'拡張子のファイルも読まない、私が知る限り。 . . . 私が思うのだが、なぜ、GradleはGroovyをもっと率直に使わないのだろうか、その予期される振る舞いを悪くゆがめるのではなく。

えーと、当該クラスを別のGradleファイル内に入れて、そのGradleファイルをスクリプト内へ「apply」することはできるが、1つの問題は、そのクラスはスクリプト内で不可視であることだ、以下が「unable to resolve class ClassB」エラーを起こすとおり。

Classes.gradle

@Gradle ソースコード
class ClassB {
	public static void staticMethodB () {
		println ("### staticMethodB!")
	}
	
	public void instanceMethodB () {
		println ("### instanceMethodB!")
	}
}

build.gradle

@Gradle ソースコード
apply ("from": "Classes.gradle")

ClassB l_classB = new ClassB ()

Special-Student-7-Rebutter
なぜだ?

Special-Student-7-Hypothesizer
その理由は、Gradleファイル毎に別のクラスローダーが使われることだ。

とすると、私はどうすべきか? . . . クラスインスタンスを、例えばプロジェクトプロパティ、へ入れれば、そのクラスインスタンスはそのスクリプト内でアクセスできる、以下がオーケーであるとおり。

Classes.gradle

@Gradle ソースコード
class ClassB {
	public static void staticMethodB () {
		println ("### staticMethodB!")
	}
	
	public void instanceMethodB () {
		println ("### instanceMethodB!")
	}
}

ext.classB = new ClassB ()

build.gradle

@Gradle ソースコード
classB.instanceMethodB ()

Special-Student-7-Rebutter
That is as expected. それは予期されるとおりだ。

Special-Student-7-Hypothesizer
しかし、クラス自体、クラスインスタンスではなくて、がスクリプト内で使われなければならなかったらどうか?例えば、あるカスタムタスククラスがスクリプト内で使われなければならない、そのクラスからタスクを生成するためには。

実のところ、そのクラスをプロジェクトプロパティへ入れることができる、以下がオーケーであるとおり。

Classes.gradle

@Gradle ソースコード
class ClassB {
	public static void staticMethodB () {
		println ("### staticMethodB!")
	}
	
	public void instanceMethodB () {
		println ("### instanceMethodB!")
	}
}

class CustumTaskA extends DefaultTask {
	@TaskAction
	void action () {
		println ("### CustumTaskA working!")
	}
}

ext.ClassB = ClassB.class
ext.CustumTaskA = CustumTaskA.class

build.gradle

@Gradle ソースコード
ClassB.staticMethodB ()

task ("custumTaskA", type: CustumTaskA, {
})

しかし、それでも、そのクラスから'new'オペレーターでインスタンスを生成することができない、スクリプト内にて。

私はそういう必要性に遭遇したことはないが、もしもしたら、私はファクトリーメソッドを持つことができる、以下のように。

Classes.gradle

@Gradle ソースコード
class ClassB {
	public static void staticMethodB () {
		println ("### staticMethodB!")
	}
	
	public static ClassB createInstance () {
		return new ClassB ()
	}
	
	public void instanceMethodB () {
		println ("### instanceMethodB!")
	}
}

build.gradle

@Gradle ソースコード
def l_classB = ClassB.createInstance ()
l_classB.instanceMethodB ()

「l_classB」の変数タイプを'ClassB'に指定はできないが、残念なことに!


4: スクリプトオブジェクトまたはプロジェクトオブジェクトをクラスへインジェクトする


Special-Student-7-Hypothesizer
既に言ったとおり、アウタークラスであることの不便は、そのクラスはスクリプトフィールド群、スクリプトプロパティ群、プロジェクトプロパティ群にアクセスできない(少なくとも直接には)ことである。

もしも、そのクラスはそれらの一部にアクセスする必要があるというのであれば、それらは、そのクラスのインスタンスから可視であるようにされなければならない、何らかの方法で。

私は通常、プロジェクトオブジェクトを当該クラスインスタンスへインジェクトする。

もしも、そのクラスは'new'オペレーターでインスタンス化するものであるというのであれば、そのプロジェクトオブジェクトをコンストラクタ引数として渡すことができる、以下のように。

Classes.gradle

@Gradle ソースコード
class ClassB {
	private Project i_project
	
	public static void staticMethodB () {
		println ("### staticMethodB!")
	}
	
	public static ClassB createInstance (Project a_project) {
		return new ClassB (a_project)
	}
	
	public ClassB (Project a_project) {
		i_project = a_project
	}
	
	public void instanceMethodB () {
		println ("### instanceMethodB!")
	}
}

もしも、そのクラスはタスククラス(それは'task'メソッドによってインスタンス化される)であるというのであれば、プロジェクトオブジェクトをコンフィギュレーションクロージャにてセットすることができる、以下のように。

Classes.gradle

@Gradle ソースコード
class CustumTaskA extends DefaultTask {
	protected Project i_project
	
	@TaskAction
	void action () {
		println ("### CustumTaskA working!")
	}
}

ext.CustumTaskA = CustumTaskA.class

build.gradle

@Gradle ソースコード
task ("custumTaskA", type: CustumTaskA, {
	i_project = project
})


5: プロジェクト拡張: 私が用いるストラテジー


Special-Student-7-Hypothesizer
私が用いるストラテジーは、プロジェクト拡張を持つことである。

Special-Student-7-Rebutter
それは何だ?

Special-Student-7-Hypothesizer
プロジェクト拡張は、タスク群で共通に使われるフィールド群やメソッド群を持つクラスのインスタンスであるプロジェクトプロパティだ。

例えば、私は、あるプロジェクト拡張クラスを定義し、そのインスタンスをプロジェクトプロパティとしてセットする、以下のように。

Classes.gradle

@Gradle ソースコード
@groovy.transform.CompileStatic
class ProjectExtension {
	private Project i_project
	public String i_parameterA
	public String i_parameterB
	
	public ProjectExtension (Project a_project) {
		i_project = a_project
		i_parameterA = "parameter A"
		i_parameterB = "parameter B"
	}
	
	public void methodA () {
		println ("### methoidA!")
	}
	
	public void methodB () {
		println ("### methoidB!")
	}
}

ext.extension = new ProjectExtension (project)

私はなぜ、そうしたデータやファンクション群を直接にプロジェクトプロパティ群としてセットしないのだろうか、以下のように?

build.gradle

@Gradle ソースコード
ext.i_parameterA = "parameter A"
ext.i_parameterB = "parameter B"
ext.methodA = {
	println ("### methoidA!")
}
ext.methodB = {
	println ("### methoidB!")
}

理由は、そうしたファンクション群はクロージャであり、クロージャは確かに便利であるが、スタティックにコンパイルできないことで、パフォーマンス上不利になる。

なぜ、そのクラスインスタンスはプロジェクトプロパティとしてセットされなければならないのか?

えーと、特にされなければならないことはないが、プロジェクトオブジェクトはクラスインスタンス群へ広くインジェクトされるよう意図されており、また、勿論、スクリプト内で可視であるので、クラスインスタンスはどこからでも可視になるだろう、そうすれば。


参考資料


<このシリーズの前の記事 | このシリーズの目次 |