2010-09-13 46 views
8

構建器實現Cloneable並覆蓋clone(),而不是複製構建器的每個字段,不可變類保留構建器的專用克隆。這可以很容易地返回一個新的構建器,並創建一個不可變實例的稍微修改後的副本。這是一個不可變類和Builder模式的有效Java實現嗎?

這樣我可以去

MyImmutable i1 = new MyImmutable.Builder().foo(1).bar(2).build(); 
MyImmutable i2 = i1.builder().foo(3).build(); 

Cloneable接口被認爲是打破幾分,但是任何違反這一良好的Java編碼實踐,有沒有什麼問題,這個建構?

final class MyImmutable { 
    public int foo() { return builder.foo; } 
    public int bar() { return builder.bar; } 
    public Builder builder() { return builder.clone(); } 
    public static final class Builder implements Cloneable { 
    public Builder foo(int val) { foo = val; return this; } 
    public Builder bar(int val) { bar = val; return this; } 
    public MyImmutable build() { return new MyImmutable(this.clone()); } 
    private int foo = 0; 
    private int bar = 0; 
    @Override public Builder clone() { try { return (Builder)super.clone(); } catch(CloneNotSupportedException e) { throw new AssertionError(); } } 
    } 
    private MyImmutable(Builder builder) { this.builder = builder; } 
    private final Builder builder; 
} 

回答

5

通常,從Builder構建的類沒有任何關於構建器的專業知識。這是不可改變的將有一個構造爲foo和酒吧提供值:

public final class MyImmutable { 
    public final int foo; 
    public final int bar; 
    public MyImmutable(int foo, int bar) { 
    this.foo = foo; 
    this.bar = bar; 
    } 
} 

的建設者將是一個單獨的類:

public class MyImmutableBuilder { 
    private int foo; 
    private int bar; 
    public MyImmutableBuilder foo(int val) { foo = val; return this; } 
    public MyImmutableBuilder bar(int val) { bar = val; return this; } 
    public MyImmutable build() { return new MyImmutable(foo, bar); } 
} 

如果你願意,你可以添加一個靜態方法來MyImmutable從現有的MyImmutable實例開始:

public static MyImmutableBuilder basedOn(MyImmutable instance) { 
    return new MyImmutableBuilder().foo(instance.foo).bar(instance.bar); 
} 
+0

我試圖削減一些角落,並避免顯式複製字段。 「basedOn」方法非常清晰,但它需要我複製字段(再次)。也許我太懶惰了。 – Aksel 2010-09-13 19:52:18

+0

很棒的建議basedOn方法:) – troig 2017-03-23 13:36:01

2

我以前沒見過這種方法,但看起來好像可以正常工作。

基本上,它使得構建器模式實現起來相對簡單,代價是略高的運行時間開銷(額外的對象+克隆操作+訪問器函數中的間接級別,可能會或可能不會被編譯出來)。

您可能需要考慮的潛在變體:如果您將構建器對象本身設爲不可變的,則不需要防禦性地克隆它們。這可能是一個全面的勝利,尤其是如果你比建造者更頻繁地構建對象。

+0

讓建設者不變是一個有趣的建議,但也許我應該按照約書亞布洛赫斯的優秀例子來信,並停止嘗試變得聰明。好吧,出現錯誤,顯然Joshua Bloch非常聰明。我的意思是關於他優秀的書的第2項。 – Aksel 2010-09-13 19:54:37

+0

使用如上所述的方法,使用來自不同不可變對象的任意數量更改生成新的不可變對象將需要兩個防禦副本。沒有可變建設者,進行N次修改需要N個防禦副本。哪種方法更好取決於要進行的更改次數。 – supercat 2013-03-18 21:21:31

2

您的實現類似於Josh Bloch的Effective Java第2版中詳述的實現。

爭論的一點是您的build()方法。如果單個構建器創建一個不可變實例,考慮到它的工作已經完成,允許構建器再次使用是否公平?這裏需要注意的是,即使你正在創建一個不可變的對象,你的構建器的可變性可能會導致一些相當「令人驚訝」的錯誤。

要糾正這種情況,可能會提示構建方法應該創建實例,然後使構建器無法再次構建對象,需要新的構建器。儘管鍋爐板看起來很乏味,但後來獲得的好處超過了目前所需的努力。然後,實現類可以接收一個作爲構造函數參數的實例,但是構建器應該讓實例提取所需的所有狀態,釋放構建器實例並保留對有問題的數據的最終引用。

+0

「Effective Java」注意到這是一個好處:「可以使用單個構建器來構建多個對象。構建器的參數可以在對象創建之間調整以改變對象。 – slim 2011-07-20 15:06:43

+0

是的。您不想要的是以前使用該構建器來干擾當前正在使用的新對象。 – gpampara 2011-07-21 09:29:29

相關問題