2010-09-13 14 views
8

Der Builder implementiert Cloneable und überschreibt clone(). Anstatt jedes Feld des Builders zu kopieren, behält die unveränderliche Klasse einen privaten Clone des Builders bei. Dies macht es einfach, einen neuen Builder zurückzugeben und leicht modifizierte Kopien einer unveränderlichen Instanz zu erstellen.Ist dies eine gültige Java-Implementierung einer unveränderlichen Klasse und des Builder-Musters?

So kann ich

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

Die klonbar Schnittstelle etwas gebrochen wird gesagt, gehen kann, werden aber nicht alle diese verletzen gute Java-Codierung Praxis gibt es irgendwelche Probleme mit diesem Konstrukt?

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; 
} 

Antwort

5

Im Allgemeinen hat die aus dem Builder erstellte Klasse keine speziellen Kenntnisse des Builders. Das ist Immutable würde einen Konstruktor haben den Wert für foo und bar zu liefern:

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

Der Bauherr wäre eine separate Klasse sein:

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); } 
} 

Wenn Sie wollten, eine statische Methode MyImmutable könnte hinzufügen Builder aus einer bestehenden MyImmutable Instanz zu starten:

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

Ich habe versucht, einige Ecken zu schneiden und vermeiden, Felder explizit zu kopieren. Die "basedOn" -Methode ist wirklich klar, aber ich würde Felder (wieder) kopieren müssen. Vielleicht bin ich zu faul. – Aksel

+0

Großartiger Vorschlag der basedOn Methode :) – troig

2

Ich habe diesen Ansatz nicht vorher gesehen, aber sieht aus wie es würde gut funktionieren.

Grundsätzlich macht es das Builder-Muster relativ einfach zu implementieren, auf Kosten von etwas höheren Laufzeitaufwand (zusätzliche Objekte + Cloning-Operationen + eine Ebene der Indirektion in Accessor-Funktionen, die kompiliert werden können oder nicht).

Eine mögliche Variante, die Sie berücksichtigen sollten: Wenn Sie die Builder-Objekte selbst unveränderbar machen, müssen Sie sie nicht defensiv klonen. Dies kann ein Gesamtgewinn sein, besonders wenn Sie Objekte häufiger erstellen, als Sie die Builder ändern.

+0

Den Erbauer unveränderlich zu machen ist ein interessanter Vorschlag, aber vielleicht sollte ich Joshua Blochs ausgezeichnetem Beispiel genau folgen und versuchen, clever zu sein. Okay, das ist falsch gelaufen, klar Joshua Bloch ist sehr schlau. Was ich meinte, war Folge 2 seines ausgezeichneten Buches. – Aksel

+0

Mit dem Ansatz wie angegeben, erfordert das Erzeugen eines neuen unveränderlichen Objekts mit einer beliebigen Anzahl von Änderungen von einem anderen unveränderlichen Objekt zwei defensive Kopien.Ohne einen veränderlichen Builder würde N Änderungen erfordern N Verteidigung Kopien. Welcher Ansatz besser ist, hängt von der Anzahl der vorzunehmenden Änderungen ab. – supercat

2

Ihre Implementierung der Umsetzung ähnlich ist detailliert in Josh Blochs Effective Java 2nd Edition.

Ein Streitpunkt wäre Ihre build() Methode. Wenn ein einzelner Builder eine unveränderbare Instanz erstellt, wäre es fair, den Builder erneut zu verwenden, wenn man bedenkt, dass die Arbeit erledigt ist? Die Vorsicht hier ist, dass, obwohl Sie ein unveränderliches Objekt erstellen, die Wandlungsfähigkeit Ihres Builders zu einigen eher "überraschenden" Fehlern führen kann.

Um dies zu korrigieren, wird möglicherweise vorgeschlagen, dass die Erstellungsmethode die Instanz erstellen sollte und der Builder dann nicht in der Lage sein sollte, das Objekt erneut zu erstellen, sodass ein neuer Ersteller erforderlich ist. Obwohl die Kesselplatte langwierig erscheinen mag, überwiegen die später zu gewinnenden Vorteile die derzeit erforderlichen Anstrengungen. Die Implementierungsklasse kann dann eine Builder -Instanz als Konstruktorparameter erhalten, aber der Builder sollte dann die Instanz veranlassen, den benötigten Zustand auszulösen, die Builder-Instanz freizugeben und die endgültigen Verweise auf die fraglichen Daten beizubehalten.

+0

"Effektives Java" stellt dies als einen Vorteil fest: "Ein einzelner Generator kann verwendet werden, um mehrere Objekte zu erstellen. Die Parameter des Builders können zwischen Objektkreationen angepasst werden, um die Objekte zu variieren." – slim

+0

Ja, ist es. Was Sie nicht möchten, ist die vorherige Verwendung des Builders, um das neue Objekt zu stören, mit dem es gerade erstellt wird. – gpampara

Verwandte Themen