2010-09-13 17 views
8

The Builder implementa Cloneable y anula clone() y en lugar de copiar todos los campos del generador, la clase inmutable conserva un clon privado del generador. Esto hace que sea más fácil devolver un nuevo generador y crear copias ligeramente modificadas de una instancia inmutable.¿Es esta una implementación válida de Java de una clase inmutable y el patrón de Constructor?

De esta manera puedo ir

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

La interfaz Cloneable se dice que está un poco roto, pero nada de esto viola las buenas prácticas de codificación Java, ¿hay algún problema con esta construcción?

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

Respuesta

5

En general, la clase construida a partir del Constructor no tiene ningún conocimiento especializado del constructor. Eso es inmutable tendría un constructor para proporcionar el valor de foo y bar:

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

El constructor sería una clase separada:

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

Si quisiera, podría agregar un método estático a MyImmutable constructor para iniciar una instancia de MyImmutable existente:

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

Estaba tratando de cortar algunas esquinas y evitar copiar campos explícitamente. El método "basedOn" es muy claro pero me obligaría a copiar campos (nuevamente). Tal vez estoy siendo muy perezoso. – Aksel

+0

Excelente sugerencia del método basedOn :) – troig

2

No he visto este enfoque antes, pero parece que funcionaría bien.

Básicamente hace que el patrón de compilador sea relativamente simple de implementar, a expensas de una sobrecarga de tiempo de ejecución ligeramente superior (objetos extra + operaciones de clonación + un nivel de direccionamiento indirecto en las funciones de acceso que pueden compilarse o no).

Una posible variación que quizás desee considerar: si hace que los objetos del constructor sean inmutables, no tendrá que clonarlos a la defensiva. Esto podría ser una ganancia general, especialmente si construyes objetos mucho más frecuentemente que si modificas los constructores.

+0

Hacer el constructor inmutable es una sugerencia interesante, pero tal vez debería seguir el excelente ejemplo de Joshua Blochs al pie de la letra y dejar de tratar de ser inteligente. Bueno, eso salió mal, claramente Joshua Bloch es muy inteligente. Lo que quise decir fue seguir el ítem 2 de su excelente libro. – Aksel

+0

Con el enfoque como se indica, producir un nuevo objeto inmutable con cualquier cantidad de cambios desde un objeto inmutable diferente requerirá dos copias defensivas.Sin un constructor mutable, hacer N cambios requeriría N copias defensivas. Qué enfoque es mejor dependerá de la cantidad de cambios que se realicen. – supercat

2

Su aplicación es similar a la aplicación se detalla en el de Josh Bloch Effective Java 2ª edición.

Un punto de controversia sería su método build(). Si un solo constructor crea una instancia inmutable, ¿sería justo permitir que el constructor se vuelva a utilizar, teniendo en cuenta que se ha realizado el trabajo? La precaución aquí es que a pesar de que está creando un objeto inmutable, la mutabilidad de su constructor puede dar como resultado unos pocos errores bastante "sorprendentes".

Para corregir esto, se puede sugerir que el método de compilación debe crear la instancia y luego hacer que el constructor sea incapaz de construir el objeto nuevamente, requiriendo un nuevo constructor. Aunque la placa de la caldera puede parecer tediosa, los beneficios que se cosecharán más tarde superan el esfuerzo requerido en la actualidad. La clase de implementación puede recibir una instancia Builder como parámetro constructor, pero el constructor debe permitir que la instancia saque todo el estado necesario, liberando la instancia del generador y conservando las referencias finales a los datos en cuestión.

+0

"Effective Java" nota esto como un beneficio: "Se puede usar un solo constructor para construir múltiples objetos. Los parámetros del constructor se pueden ajustar entre creaciones de objeto para variar los objetos". – slim

+0

Sí, lo es. Lo que no desea es el uso anterior del generador para interferir con el nuevo objeto que se está utilizando actualmente para crear. – gpampara

Cuestiones relacionadas