2012-07-07 32 views
21

我有一個有很多字段的JAVA類。他們基本上應該設置在構造器階段,並且永遠不會改變。在語義上,這個類是不可變的。「不可變類」的Java構造函數有很多帶默認值的字段?

public class A{ 
    final int a; 
    final short b; 
    final double e; 
    final String f; 
    final String g; 
    //and more 
} 

的問題是,通常這些字段的默認值,因此我不希望總是負擔與他們的構造函數的用戶。大多數時候,他們只需要設置幾個。有幾種方法可以解決這個問題:

  1. 我需要大量具有不同簽名的構造函數。
  2. 創建一堆這些字段的設置方法,並只設置那些非默認值。但是這不知何故表明除了不可變性之外的不同語義。
  3. 創建一個可變的新參數類,並將該類用作構造函數。

沒有一個是完全令人滿意的。還有其他方法嗎?謝謝。 一種方式

回答

27

我會用一個參數類的組合和流暢的建設者API創建的參數(你本來也應該這樣做!):

public class A { 
    private final int a; 
    private final short b; 
    private final double e; 
    private final String g; 

    public static class Aparam { 
     private int a = 1; 
     private short b = 2; 
     private double e = 3.141593; 
     private String g = "NONE"; 

     public Aparam a(int a) { 
      this.a = a; 
      return this; 
     } 

     public Aparam b(short b) { 
      this.b = b; 
      return this; 
     } 

     public Aparam e(double e) { 
      this.e = e; 
      return this; 
     } 

     public Aparam g(String g) { 
      this.g = g; 
      return this; 
     } 

     public A build() { 
      return new A(this); 
     } 
    } 

    public static Aparam a(int a) { 
     return new Aparam().a(a); 
    } 

    public static Aparam b(short b) { 
     return new Aparam().b(b); 
    } 

    public static Aparam e(double e) { 
     return new Aparam().e(e); 
    } 

    public static Aparam g(String g) { 
     return new Aparam().g(g); 
    } 

    public static A build() { 
     return new Aparam().build(); 
    } 

    private A(Aparam p) { 
     this.a = p.a; 
     this.b = p.b; 
     this.e = p.e; 
     this.g = p.g; 
    } 

    @Override public String toString() { 
     return "{a=" + a + ",b=" + b + ",e=" + e + ",g=" + g + "}"; 
    } 
} 

然後創建A的情況如下:

A a1 = A.build(); 
A a2 = A.a(7).e(17.5).build(); 
A a3 = A.b((short)42).e(2.218282).g("fluent").build(); 

A類是不可變的,參數是可選的,接口流暢。

+1

你甚至不需要在構建器中使用getter,而A的構造函數可以是私有的。這也允許檢查build()方法中的參數而不是構造函數。 – 2012-07-07 22:09:17

+0

是的。我考慮讓A的ctor私密。這可能更乾淨。 – 2012-07-07 22:11:51

+0

另一個優點是,構建器可以根據參數選擇返回A,ABis或ATer(Abis和Ater是A的子類)的實例。 – 2012-07-07 22:14:47

19

兩件事情可以做:

+4

建造者+1。 – 2012-07-07 22:01:09

+0

我不認爲構建器模式適用於不可變類。 – zggame 2012-07-09 02:57:51

+0

@zggame:當然不是!您使用構建器來構建另一個對象。 – 2012-07-09 13:51:29

0

一個有趣的方法是創建一個構造函數一個Map<String,Object>因爲它包含輸入用戶想要指定的值。

構造函數可以使用映射中提供的值(如果存在),否則使用默認值。

編輯:

我覺得隨機downvoters已經完全忽略了一點 - 這並不總是將是最好的選擇,但它是有幾個好處一個有用的技術:

  • 它簡潔並且避免了創建單獨的構造器/構建器類的需要
  • 它允許輕鬆地編程構建參數集(例如,如果您正在從解析的DSL構建對象)
  • 這是一種經常使用並經過驗證可以在動態語言中使用的技術。你只需要編寫體面測試
+1

這導致了很多錯誤,主要是類型兼容性,我會說在這種情況下,構建器方法會更好(如Jordão所建議的) – 2012-07-07 22:01:17

+0

這是一種動態語言技術....您正在交易靜態類型檢查便利/靈活性。如果你喜歡這個方法,那麼你可以選擇你,但是如果你編寫好的測試,我發現它不是問題。 – mikera 2012-07-07 22:03:44

+0

這是JavaScript的方式,擅長動態打字的語言,但我不會用Java來完成。如果所有參數都是相同的類型,可以工作。儘管如此,我認爲鮑勃馬丁有些話要說「傳遞哈希映射」並不是一個好主意。沒有-1或+1。 – 2012-07-07 22:03:56

0

有很多字段可能表示一個班級做得太多。

也許你可以將類分成幾個不可變類,並將這些類的實例傳遞給其他類的構造函數。這會限制構造函數的數量。

1

這只是一個半認真的建議,但我們可以修改mikera's answer爲類型安全。

說我們有:

public class A { 
    private final String foo; 
    private final int bar; 
    private final Date baz; 
} 

然後我們寫:

public abstract class AProperty<T> { 
    public static final AProperty<String> FOO = new AProperty<String>(String.class) {}; 
    public static final AProperty<Integer> BAR = new AProperty<Integer>(Integer.class) {}; 
    public static final AProperty<Date> BAZ = new AProperty<Date>(Date.class) {}; 

    public final Class<T> propertyClass; 

    private AProperty(Class<T> propertyClass) { 
     this.propertyClass = propertyClass; 
    } 
} 

而且:先進的設計模式和/或模糊的Java的技巧就知道這是

public class APropertyMap { 
    private final Map<AProperty<?>, Object> properties = new HashMap<AProperty<?>, Object>(); 

    public <T> void put(AProperty<T> property, T value) { 
     properties.put(property, value); 
    } 
    public <T> T get(AProperty<T> property) { 
     return property.propertyClass.cast(properties.get(property)); 
    } 
} 

愛好者一個類型安全的異構容器。只是很感謝,我也沒有使用getGenericSuperclass()

然後,早在目標類:

public A(APropertyMap properties) { 
    foo = properties.get(AProperty.FOO); 
    bar = properties.get(AProperty.BAR); 
    baz = properties.get(AProperty.BAZ); 
} 

這一切像這樣使用:

APropertyMap properties = new APropertyMap(); 
properties.put(AProperty.FOO, "skidoo"); 
properties.put(AProperty.BAR, 23); 
A a = new A(properties); 

只是爲了lulz,我們甚至可以給地圖流暢的界面:

public <T> APropertyMap with(AProperty<T> property, T value) { 
    put(property, value); 
    return this; 
} 

,它可以讓呼叫者寫:

A a = new A(new APropertyMap() 
    .with(AProperty.FOO, "skidoo") 
    .with(AProperty.BAR, 23)); 

你可以對此做很多小的改進。 AProperty中的類型可以更優雅地處理。如果你遇到這種情況,APropertyMap可以有一個靜態工廠而不是一個構造函數,允許更流暢的代碼風格。 APropertyMap可能會增長一個build方法,該方法調用A的構造函數,實質上將其轉化爲構建器。

你也可以使這些對象中的一些更通用。 APropertyAPropertyMap可能具有功能位的通用基類,具有非常簡單的A特定子類。

如果您感覺特別是企業,並且您的域對象是JPA2實體,那麼您可以使用元模型屬性作爲屬性對象。這使得地圖/構建器可以做更多的工作,但它仍然非常簡單;我有一個45行的通用構建器,每個實體的子類包含一個單行方法。

+0

不錯。我喜歡靜態打字。良好的妥協,我可能能夠用於另一個案例,我們有一個巨大的Map 。謝謝。 – zggame 2012-07-09 03:01:55

相關問題