2015-02-07 101 views
4

我正試圖在Groovy中開發一個項目,並且一直在尋找我的代碼並試圖找到可以用更通俗的Groovy替換的區域,直到找到another issue I've been having的解決方案。有沒有辦法讓@Builder註釋適用於不可變類?

我已經開始更深入地研究AST轉換註釋的使用 - 它們幫助大幅減少了我在某些地方編寫的代碼量。不過,我有一個問題,使用groovy.transform.builder.Builder註釋與我的一個不可變的值類。此註釋的來源託管於here

問題是,註釋似乎使構建器直接設置buildee的值,而不是存儲值的副本並將它們傳遞給buildee的構造函數。當您嘗試將其與不可變類一起使用時,會導致ReadOnlyPropertyException

有四種可能的構建器策略,您可以選擇這個註釋,其中我試過DefaultStrategy,ExternalStrategyInitializerStrategy。但是,所有這些都造成了問題。

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

+0

'@ Immutable'默認是最終的。你能否提供一個最小的失敗版本的代碼?現在很難遵循。也爲什麼'@ PackageScope'? – cfrick 2015-02-07 22:24:04

+0

@cfrick我會盡量把東西放在一起,當我完成時更新你。 '@ PackageScope'因爲最好儘可能限制範圍。我想'Version'接口是公共的,但具體的實現是包私有的,這樣客戶端就不得不通過'VersionFactory'來構造它們(靜態工廠方法通常比構造器[Effective Java Item 1]更好)。我還沒有充分發揮這一點,但可以確定這是最好的方法,但如果不能解決問題,我會嘗試一些不同的方法。 – Tagc 2015-02-07 22:54:33

+0

@cfrick製作SSCCE時,我發現我濫用了初始化工具。我糾正了我的錯誤,現在它似乎工作正常,所以我將你的答案標記爲已接受。然而,我並不喜歡初始化方法(特別是因爲它需要拋棄@ PackageScope),所以我只是要堅持我曾經使用過的老式的實際源代碼方法之前。謝謝你的幫助。 – Tagc 2015-02-08 12:01:18

回答

3

你的代碼中有失敗,因爲選擇的策略有基本的作用:

def v = new Value().with{ setValue(1); return it } 

,這不能@Immutable對象來完成。

根據docs,只有InitializerStrategy,可以明確應對@Immutable

您可以將InitializerStrategy與@Canonical和@Immutable結合使用。如果您的@Builder註釋沒有顯式包含或排除註釋屬性,但您的@Canonical註釋確實會這樣做,@Canonical的註釋屬性將被重用於@Builder。

例如,

import groovy.transform.* 
import groovy.transform.builder.* 

@Immutable 
@ToString 
@Builder(prefix='set', builderStrategy=InitializerStrategy) 
class Value { 
    int value 
} 

def builder = Value.createInitializer().setValue(1) 
assert new Value(builder).toString()=='Value(1)' 

這取決於你的行爲,這是更醜陋的語法,你可能會更好使用基於地圖的c'tors。即使沒有例如@TypeChecked a new Value(vlaue: 666)將產生一個錯誤,並留下參數(對於具有多個屬性的類)將使它們保留null

+0

感謝您的回覆。 「根據你的要求,這是更醜陋的語法,你可能會更好使用基於地圖的c'tors。」這是一種可能性,但正如我在JIRA討論中提到的那樣,使用構建器可以促進與Java的互操作性。你的方法使得所有的東西都是可編譯的,所以它似乎是沿着正確的路線,因此我已經把它提升了。但是,現在我得到了'IllegalAccessError's,所以我恐怕現在不能接受這個答案,因爲其他人也可能會遇到這個問題。我已更新原始帖子。 – Tagc 2015-02-07 22:00:01

相關問題