2008-11-20 11 views
5

Tengo mucha curiosidad sobre la posibilidad de proporcionar inmutabilidad para beans Java (por beans me refiero a clases con un constructor vacío que proporciona getters y setters para miembros). Claramente estas clases no son inmutables y cuando se usan para transportar valores desde la capa de datos, esto parece ser un problema real.Frijoles inmutables en Java

Un enfoque a este problema se ha mencionado aquí en StackOverflow llamado "Patrón de objetos inmutables en C#" donde el objeto se congela una vez que se ha construido por completo. Tengo un enfoque alternativo y me gustaría escuchar las opiniones de las personas al respecto.

El patrón implica dos clases Inmutable y Mutable donde Mutable e Immutable implementan una interfaz que proporciona métodos de bean no mutantes.

Por ejemplo

public interface DateBean { 
    public Date getDate(); 
    public DateBean getImmutableInstance(); 
    public DateBean getMutableInstance(); 
} 

public class ImmutableDate implements DateBean { 
    private Date date; 

ImmutableDate(Date date) { 
    this.date = new Date(date.getTime()); 
} 

public Date getDate() { 
    return new Date(date.getTime()); 
} 

    public DateBean getImmutableInstance() { 
     return this; 
    } 

    public DateBean getMutableInstance() { 
     MutableDate dateBean = new MutableDate(); 
     dateBean.setDate(getDate()); 
     return dateBean; 
    } 
} 

public class MutableDate implements DateBean { 
    private Date date; 

public Date getDate() { 
    return date; 
} 

public void setDate(Date date) { 
    this.date = date; 
} 

public DateBean getImmutableInstance() { 
    return new ImmutableDate(this.date); 
} 

    public DateBean getMutableInstance() { 
     MutableDate dateBean = new MutableDate(); 
     dateBean.setDate(getDate()); 
     return dateBean; 
    } 
} 

Este enfoque permite que el grano que se construirá utilizando la reflexión (por las convenciones habituales) y también nos permite convertir una variante inmutable en la oportunidad más cercana. Desafortunadamente, existe claramente una gran cantidad de repetición por frijol.

Estoy muy interesado en escuchar el enfoque de otras personas sobre este tema. (Mis disculpas por no proporcionar una buena pregunta, que se puede responder más que discutido :)

Respuesta

2

Creo que haría uso de la pauta delegación - hacer una clase ImmutableDate con un solo miembro DateBean que debe especificarse en el constructor:

public class ImmutableDate implements DateBean 
{ 
    private DateBean delegate; 

    public ImmutableDate(DateBean d) 
    { 
     this.delegate = d; 
    } 

    public Date getDate() 
    { 
     return delegate.getDate(); 
    } 
} 

Si alguna vez tengo que forzar la inmutabilidad en un DateBean d, solo nuevo ImmutableDate (d) en él. Podría haber sido inteligente y haberme asegurado de no haber delegado al delegado, pero entiendes la idea. Eso evita el problema de que un cliente intente convertirlo en algo mutable. Esto es muy similar a la JDK hace con Collections.unmodifiableMap(), etc (en esos casos, sin embargo, las funciones de mutación todavía tiene que ser implementado, y se codifican para lanzar una excepción en tiempo de ejecución. Mucho más fácil si usted tiene una interfaz de base sin la mutadores).

Una vez más, es tedioso código repetitivo, pero es el tipo de cosas que un buen IDE como Eclipse puede generar automáticamente para usted con sólo unos pocos clics del ratón.

Si es el tipo de cosas que terminan haciendo que una gran cantidad de objetos de dominio, es posible que desee considerar el uso de proxies dinámicos o tal vez incluso AOP. Sería relativamente fácil construir un proxy para cualquier objeto, delegar todos los métodos get y atrapar o ignorar los métodos establecidos, según corresponda.

+0

Esa es una muy buena idea. Eso encaja el problema muy elegantemente. Saludos Chris. –

+0

Si se trata de un objeto de fecha retenido en DateBean, tendrá que crear una nueva Fecha en get. De lo contrario, puede modificar la fecha a través de ImmutableDate.getDate(). Set .... – monksy

3

Algunos comentarios (no necesariamente problemas):

  1. La clase Date es en sí misma mudable por lo que va a copiar correctamente a proteger la inmutabilidad, pero personalmente prefiero convertir a largo en el constructor y devolver una nueva Fecha (longValue) en el getter.
  2. Ambos métodos getWhateverInstance() devuelven DateBean que requerirá de conversión, en su lugar puede ser una idea cambiar la interfaz para devolver el tipo específico.
  3. Habiendo dicho todo lo que me gustaría tener solo dos clases una mutable y una inmutable, compartiendo una interfaz común (es decir, solo obtener) si corresponde. Si crees que habrá una gran cantidad de conversiones de ida y vuelta, agrega un constructor de copias a ambas clases.
  4. Prefiero que las clases inmutables declaren campos como final para que el compilador aplique la inmutabilidad también.

e.g.

public interface DateBean { 
    public Date getDate(); 
} 

public class ImmutableDate implements DateBean { 
    private final long date; 

    ImmutableDate(long date) { 
     this.date = date; 
    } 

    ImmutableDate(Date date) { 
     this(date.getTime()); 
    } 

    ImmutableDate(DateBean bean) { 
     this(bean.getDate()); 
    } 

    public Date getDate() { 
     return new Date(date); 
    } 
} 


public class MutableDate implements DateBean { 
    private long date; 

    MutableDate() {} 

    MutableDate(long date) { 
     this.date = date; 
    } 

    MutableDate(Date date) { 
     this(date.getTime()); 
    } 

    MutableDate(DateBean bean) { 
     this(bean.getDate()); 
    } 

    public Date getDate() { 
     return new Date(date); 
    } 

    public void setDate(Date date) { 
     this.date = date.getTime(); 
    } 

} 
+0

Gracias por sus comentarios. Estoy de acuerdo en que el constructor de copias es una mejora significativa sobre los métodos de fábrica de interfaz. La nota sobre los miembros finales está bien tomada. Aclamaciones. –

1

Uso las interfaces y la fundición para controlar la mutabilidad de los frijoles. No veo una buena razón para complicar mis objetos de dominio con métodos como getImmutableInstance() y getMutableInstance().

¿Por qué no hacer uso de la herencia y la abstracción? p.ej.

public interface User{ 

    long getId(); 

    String getName(); 

    int getAge(); 

} 

public interface MutableUser extends User{ 

    void setName(String name); 

    void setAge(int age); 

} 

Esto es lo que el cliente del código va a hacer:

public void validateUser(User user){ 
    if(user.getName() == null) ... 
} 

public void updateUserAge(MutableUser user, int age){ 
    user.setAge(age); 
} 

lo hace responde a su pregunta?

YC

+0

Supongo que quiere decir que la interfaz MutableUser extiende la interfaz de usuario. Huelga decir que esto todavía requiere una clase concreta. El inconveniente es que te abre a "Inmutable" siendo mutable por un simple elenco que probablemente no sea lo que quieres. –

+0

Si bien es cierto que la interfaz de usuario no proporciona métodos para mutar un objeto de usuario que no está en la cara de ella tan fuerte garantía de la inmutabilidad como me gustaría pedir de este patrón. –

+0

Aunque creo que su enfoque proporciona una buena solución de trabajo donde el tipo (Usuario) sugiere implícitamente que el Usuario no debe ser mutado. En muchos contextos esto es probablemente suficiente. Gracias. –

Cuestiones relacionadas