Clase con miembros que son mutables durante la creación pero inmutables después

22

Tengo un algoritmo que crea una colección de objetos. Estos objetos son mutables durante la creación, ya que comienzan con muy poco, pero luego se rellenan con datos en diferentes lugares dentro del algoritmo.

Una vez que se completa el algoritmo, los objetos nunca deben cambiarse; sin embargo, otras partes del software los consumen.

En estos escenarios, ¿se considera una buena práctica tener dos versiones de la clase, como se describe a continuación?

  • El mutable es creado por el algoritmo, luego
  • Al completar el algoritmo, los datos se copian en objetos inmutables que se devuelven.
Paul Richards
fuente
3
¿Podría editar su pregunta para aclarar cuál es su problema / pregunta?
Simon Bergot
También te puede interesar esta pregunta: Cómo congelar una paleta en .NET (hacer que una clase sea inmutable)
R0MANARMY

Respuestas:

46

Quizás pueda usar el patrón de construcción . Utiliza un objeto 'constructor' separado con el propósito de recopilar los datos necesarios, y cuando se recopilan todos los datos, crea el objeto real. El objeto creado puede ser inmutable.

JacquesB
fuente
Su solución fue la correcta para mis requisitos (no se mencionaron todos). Tras la investigación, los objetos devueltos no tienen todas las mismas características. La clase de constructor tiene muchos campos anulables y, cuando los datos se han acumulado, elige cuál de las 3 clases diferentes generar: estas están relacionadas entre sí por herencia.
Paul Richards
2
@Paul En ese caso, si esta respuesta resolvió su problema, debe marcarlo como aceptado.
Riking
24

Una manera simple de lograr esto sería tener una interfaz que le permita a uno leer las propiedades y llamar solo a métodos de solo lectura y una clase que implemente esa interfaz que también le permite escribir esa clase.

Su método que lo crea, trata con el primero y luego devuelve el último proporcionando solo una interfaz de solo lectura para interactuar. Esto no requeriría copia y le permite ajustar fácilmente los comportamientos que desea que estén disponibles para la persona que llama en lugar del creador.

Toma este ejemplo:

public interface IPerson 
{
    public String FirstName 
    {
        get;
    }

    public String LastName 
    {
        get;
    }
} 

public class PersonImpl : IPerson 
{
    private String firstName, lastName;

    public String FirstName 
    {
        get { return firstName; }
        set { firstName = value; }
    }

    public String LastName 
    {
        get { return lastName; }
        set { lastName = value; }
    }
}

class Factory 
{
    public IPerson MakePerson() 
    {
        PersonImpl person = new PersonImpl();
        person.FirstName = 'Joe';
        person.LastName = 'Schmoe';
        return person;
    }
}

La única desventaja de este enfoque es que uno simplemente podría lanzarlo a la clase implementadora. Si se tratara de una cuestión de seguridad, simplemente usar este enfoque es insuficiente. Una solución para esto es que puede crear una clase de fachada para ajustar la clase mutable, que simplemente presenta una interfaz con la que trabaja la persona que llama y no puede tener acceso al objeto interno.

De esta manera, ni siquiera el casting te ayudará. Ambos pueden derivar de la misma interfaz de solo lectura, pero la conversión del objeto devuelto solo le dará la clase Facade, que es inmutable ya que no cambia el estado subyacente de la clase mutable envuelta.

Vale la pena mencionar que esto no sigue la tendencia típica en la que un objeto inmutable se construye de una vez por todas a través de su constructor. Es comprensible que tenga que lidiar con muchos parámetros, pero debe preguntarse si todos estos parámetros deben definirse por adelantado o si algunos pueden introducirse más adelante. En ese caso, se debe usar un constructor simple con solo los parámetros requeridos. En otras palabras, no use este patrón si está ocultando otro problema en su programa.

Neil
fuente
1
La seguridad no es mejor al devolver un objeto de "solo lectura", porque el código que obtiene el objeto aún puede modificar el objeto usando la reflexión. Incluso una cadena se puede modificar (no copiada, modificada en el lugar) usando la reflexión.
MTilsted
El problema de seguridad se puede resolver perfectamente con una clase privada
Esben Skov Pedersen
@Esben: todavía tiene que lidiar con MS07-052: la ejecución del código da como resultado la ejecución del código . Su código se ejecuta en el mismo contexto de seguridad que su código, por lo que pueden adjuntar un depurador y hacer lo que quieran.
Kevin
Kevin1 puedes decir eso sobre todas las encapsulaciones. No estoy tratando de proteger contra la reflexión.
Esben Skov Pedersen
1
El problema con el uso de la palabra "seguridad" es que inmediatamente alguien supone que lo que yo digo que es la opción más segura es equivalente a la máxima seguridad y las mejores prácticas. Yo nunca dije tampoco. Si le está entregando la biblioteca a alguien para que la use, a menos que esté ofuscado (y a veces incluso cuando está ofuscado), puede olvidarse de garantizar la seguridad. Sin embargo, creo que todos podemos estar de acuerdo en el hecho de que si está manipulando un objeto de fachada devuelto utilizando la reflexión para recuperar el objeto interno que contiene, no está utilizando exactamente la biblioteca, ya que debería utilizarse.
Neil
8

Puede usar el patrón Builder como dice @JacquesB o, alternativamente, pensar por qué estos objetos tuyos tienen que ser mutables durante la creación.

En otras palabras, ¿por qué el proceso de crearlos tiene que extenderse en el tiempo, en lugar de pasar todos los valores requeridos al constructor y crear instancias de una vez?

Porque el generador podría ser una buena solución para el problema incorrecto.

Si el problema es que terminas con un constructor que tiene como 10 parámetros de largo, y deseas mitigarlo construyendo el objeto poco a poco, podría indicar que el diseño está en mal estado, y estos 10 valores deberían ser " ensacado "/ agrupado en unos pocos objetos ... o el objeto principal dividido en unos pocos más pequeños ...

En cuyo caso, mantén la inmutabilidad hasta el final, solo mejora el diseño.

Konrad Morawski
fuente
Sería difícil crearlo todo de una vez ya que el código realiza múltiples consultas en la base de datos para obtener los datos; compara datos en diferentes tablas y en diferentes bases de datos.
Paul Richards