我正試圖在Groovy中開發一個項目,並且一直在尋找我的代碼並試圖找到可以用更通俗的Groovy替換的區域,直到找到another issue I've been having的解決方案。有沒有辦法讓@Builder註釋適用於不可變類?
我已經開始更深入地研究AST轉換註釋的使用 - 它們幫助大幅減少了我在某些地方編寫的代碼量。不過,我有一個問題,使用groovy.transform.builder.Builder
註釋與我的一個不可變的值類。此註釋的來源託管於here。
問題是,註釋似乎使構建器直接設置buildee的值,而不是存儲值的副本並將它們傳遞給buildee的構造函數。當您嘗試將其與不可變類一起使用時,會導致ReadOnlyPropertyException
。
有四種可能的構建器策略,您可以選擇這個註釋,其中我試過DefaultStrategy
,ExternalStrategy
和InitializerStrategy
。但是,所有這些都造成了問題。
ExternalStrategy
看起來像四個最有前途的,你可以找到一個SSCCE的基礎上詳細說明問題here。
從示例的源代碼也包括在下面:
import groovy.transform.Immutable
import groovy.transform.builder.Builder as GBuilder
import groovy.transform.builder.ExternalStrategy
/*
* Uncommenting the below causes a failure:
* 'groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: value for class: Value'
*/
//@Immutable
class Value {
@GBuilder(forClass = Value, prefix = 'set', builderStrategy = ExternalStrategy)
static class Builder { }
int value
String toString() { "Value($value)" }
}
def builder = new Value.Builder()
println builder.setValue(1).build()
看起來也似乎是對此事here相關的JIRA討論。
編輯
我用下面CFrick的答案試過,使用InitializerStrategy
而非ExternalStrategy
。
現在一切編譯,但我會在運行時下面的錯誤,當我嘗試執行我的測試:
java.lang.NoClassDefFoundError: Could not initialize class com.github.tagc.semver.test.util.TestSetup
at java.lang.Class.forName(Class.java:344)
at com.github.tagc.semver.version.SnapshotDecoratorSpec.#decoratedVersion should be considered equal to minor-bumped #releaseVersion snapshot(SnapshotDecoratorSpec.groovy:36)
:
java.lang.IllegalAccessError: tried to access class com.github.tagc.semver.version.BaseVersion from class com.github.tagc.semver.version.BaseVersion$com.github.tagc.semver.version.BaseVersionInitializer
at java.lang.Class.getDeclaringClass(Class.java:1227)
at java.beans.MethodRef.set(MethodRef.java:46)
at java.beans.MethodDescriptor.setMethod(MethodDescriptor.java:117)
at java.beans.MethodDescriptor.<init>(MethodDescriptor.java:72)
at java.beans.MethodDescriptor.<init>(MethodDescriptor.java:56)
at java.beans.Introspector.getTargetMethodInfo(Introspector.java:1163)
at java.beans.Introspector.getBeanInfo(Introspector.java:426)
at java.beans.Introspector.getBeanInfo(Introspector.java:173)
at com.github.tagc.semver.version.VersionFactory.createBaseVersion(VersionFactory.groovy:34)
at com.github.tagc.semver.test.util.TestSetup.<clinit>(TestSetup.groovy:77)
at java.lang.Class.forName(Class.java:344)
at com.github.tagc.semver.version.SnapshotDecoratorSpec.#decoratedVersion should be considered equal to patch-bumped #releaseVersion snapshot(SnapshotDecoratorSpec.groovy:24)
通過類似下面的一系列異常此後其次
我現在所擁有的是一個BaseVersion
類像下面這樣:
/**
* A concrete, base implementation of {@link com.github.tagc.semver.version.Version Version}.
*
* @author davidfallah
* @since v0.1.0
*/
@Immutable
@Builder(prefix = 'set', builderStrategy = InitializerStrategy)
@PackageScope
final class BaseVersion implements Version {
// ...
/**
* The major category of this version.
*/
int major = 0
/**
* The minor category of this version.
*/
int minor = 0
/**
* The patch category of this version.
*/
int patch = 0
/**
* Whether this version is a release or snapshot version.
*/
boolean release = false
// ...
}
一家工廠,生產的這些實例:
/**
* A factory for producing base and decorated {@code Version} objects.
*
* @author davidfallah
* @since v0.5.0
*/
class VersionFactory {
// ...
/**
* Returns an instance of {@link com.github.tagc.semver.version.BaseVersion BaseVersion} constructed
* with the given parameters.
*
* @param major the major category value of the version instance
* @param minor the minor category value of the version instance
* @param patch the patch category value of the version instance
* @param release the release setting of the version instance
* @return an instance of {@code BaseVersion}
*/
static BaseVersion createBaseVersion(int major, int minor, int patch, boolean release) {
return new BaseVersion(major, minor, patch, release)
}
/**
* Returns an instance of {@link com.github.tagc.semver.version.BaseVersion BaseVersion} constructed
* with the given parameters.
*
* @param m a map of parameter names and their corresponding values corresponding to the
* construction parameters of {@code BaseVersion}.
*
* @return an instance of {@code BaseVersion}
*/
static BaseVersion createBaseVersion(Map m) {
return new BaseVersion(m)
}
/**
* Returns an instance of {@link com.github.tagc.semver.version.BaseVersion BaseVersion} constructed
* with the given parameters.
*
* @param l a list of parameter values corresponding to the construction parameters of {@code BaseVersion}.
*
* @return an instance of {@code BaseVersion}
*/
static BaseVersion createBaseVersion(List l) {
return new BaseVersion(l)
}
/**
* Returns a builder for {@link com.github.tagc.semver.version.BaseVersion BaseVersion} to specify
* the construction parameters for the {@code BaseVersion} incrementally.
*
* @return an instance of {@code BaseVersion.Builder}
*/
static Object createBaseVersionBuilder() {
return BaseVersion.builder()
}
// ...
}
爲Version
對象的測試規範類:
/**
* Test specification for {@link com.github.tagc.semver.version.Version Version}.
*
* @author davidfallah
* @since 0.1.0
*/
@Unroll
class VersionSpec extends Specification {
static exampleVersions = [
VersionFactory.createBaseVersion(major:1, minor:2, patch:3),
VersionFactory.createBaseVersion(major:0, minor:0, patch:0),
VersionFactory.createBaseVersion(major:5, minor:4, patch:3),
VersionFactory.createBaseVersion(major:1, minor:16, patch:2),
VersionFactory.createBaseVersion(major:4, minor:5, patch:8),
]
// ...
}
和其它類,嘗試創建是失敗的BaseVersion
情況下,如TestSetup
。
'@ Immutable'默認是最終的。你能否提供一個最小的失敗版本的代碼?現在很難遵循。也爲什麼'@ PackageScope'? – cfrick 2015-02-07 22:24:04
@cfrick我會盡量把東西放在一起,當我完成時更新你。 '@ PackageScope'因爲最好儘可能限制範圍。我想'Version'接口是公共的,但具體的實現是包私有的,這樣客戶端就不得不通過'VersionFactory'來構造它們(靜態工廠方法通常比構造器[Effective Java Item 1]更好)。我還沒有充分發揮這一點,但可以確定這是最好的方法,但如果不能解決問題,我會嘗試一些不同的方法。 – Tagc 2015-02-07 22:54:33
@cfrick製作SSCCE時,我發現我濫用了初始化工具。我糾正了我的錯誤,現在它似乎工作正常,所以我將你的答案標記爲已接受。然而,我並不喜歡初始化方法(特別是因爲它需要拋棄@ PackageScope),所以我只是要堅持我曾經使用過的老式的實際源代碼方法之前。謝謝你的幫助。 – Tagc 2015-02-08 12:01:18