2012-09-06 13 views
17

Por diversas razones, me gustaría comenzar a utilizar tipos más inmutables en los diseños. Por el momento, estoy trabajando en un proyecto que tiene una clase existente como esto:Diseño inmutable: tratar con locura de constructor

public class IssueRecord 
{ 
    // The real class has more readable names :) 
    public string Foo { get; set; } 
    public string Bar { get; set; } 
    public int Baz { get; set; } 
    public string Prop { get; set; } 
    public string Prop2 { get; set; } 
    public string Prop3 { get; set; } 
    public string Prop4 { get; set; } 
    public string Prop5 { get; set; } 
    public string Prop6 { get; set; } 
    public string Prop7 { get; set; } 
    public string Prop8 { get; set; } 
    public string Prop9 { get; set; } 
    public string PropA { get; set; } 
} 

Esta clase representa algún formato en disco, que realmente tiene esta cantidad de propiedades, por lo que la refactorización en pedacitos más pequeños es bastante mucho fuera de cuestión en este punto.

¿Esto significa que el constructor en esta clase realmente necesita tener 13 parámetros en un diseño inmutable? De lo contrario, ¿qué pasos debo tomar para reducir la cantidad de parámetros aceptados en el constructor si tuviera que hacer que este diseño fuera inmutable?

+0

¿Necesita algo externo a la clase establecer los valores, o puede la clase misma establecer los valores? Si la clase puede hacerlo, entonces puede tener un constructor sin parámetros. –

+1

Ver: http://stackoverflow.com/questions/355172/how-to-design-an-immutable-object-with-complex-initialization?rq=1 – aquinas

+0

¿Cómo está inicializando estas propiedades ahora? –

Respuesta

13

¿Esto significa que el constructor en esta clase realmente necesita tener 13 parámetros en un diseño inmutable?

En general, sí. Un tipo inmutable con 13 propiedades requerirá algunos medios para inicializar todos esos valores.

Si no se usan todas, o si se pueden determinar algunas propiedades en función de las otras propiedades, entonces quizás pueda tener uno o más constructores sobrecargados con menos parámetros. Sin embargo, un constructor (ya sea que el tipo sea inmutable o no) realmente debe inicializar completamente los datos para el tipo de manera que el tipo sea lógicamente "correcto" y "completo".

Esta clase representa un formato en disco que realmente tiene muchas propiedades, por lo que refactorizarlo en bits más pequeños está prácticamente descartado en este momento.

Si el "formato en disco" es algo que está siendo determinado en tiempo de ejecución, que potencialmente podría tener un método de fábrica o constructor que toma los datos de inicialización (es decir: el nombre de archivo, etc) y construye el totalmente inicializado- escribe para ti

2

Podría hacer una estructura, pero aún tendría que declarar la estructura. Pero siempre hay arrays y tal. Si son todos del mismo tipo de datos, puede agruparlos de varias maneras, como una matriz, lista o cadena. Sin embargo, parece que tienes razón, todos tus tipos inmutables deben pasar por el constructor de alguna manera, clima a través de 13 parámetros, o mediante una estructura, matriz, lista, etc. ...

+0

Oh, me acabo de dar cuenta de que la mayoría de ellos son cadenas, así que fue correcto decir que use una matriz. –

+2

Una matriz realmente no funciona porque destruye el significado semántico de los parámetros en la estructura. Alguien que use la clase debería recordar que el quinto elemento de la matriz significa "Detalles" o algo así, lo que es peor que simplemente dejar la clase mutable. –

+0

Correcto, entonces una estructura es tu única otra opción real. Además, si estoy en lo correcto, en C#, una estructura puede usar valores predeterminados, lo que podría ser útil en su situación. –

3

Quizás conserves tu clase actual tal como está, proporcionando valores predeterminados razonables si es posible y cambiar el nombre a IssueRecordOptions. Úselo como un parámetro de inicialización único para su IssueRecord inmutable.

+0

Esta es una buena opción si el requisito de inmutabilidad solo se necesita para una parte del sistema. Sin embargo, todavía está tratando con tipos mutables, ya que debe mutar el tipo original de "Opciones". –

3

Puede usar una combinación de argumentos nombrados y opcionales en su constructor. Si los valores son siempre diferentes, entonces sí, estás atrapado con un constructor insano.

14

Para disminuir el número de argumentos, puede agruparlos en conjuntos sensibles, pero para tener un objeto verdaderamente inmóvil debe inicializarlo en el método de constructor/fábrica.

Alguna variación consiste en crear clases de "generador" que puede configurar con una interfaz fluida y luego de solicitar el objeto final. Esto tendría sentido si realmente planea crear muchos de dichos objetos en diferentes lugares del código, de lo contrario, muchos argumentos en un solo lugar pueden ser una compensación aceptable.

var immutable = new MyImmutableObjectBuilder() 
    .SetProp1(1) 
    .SetProp2(2) 
    .Build(); 
0

Si su intención es prohibir las asignaciones durante el tiempo de compilación, tendrá que ceñirse a las asignaciones del constructor y los instaladores privados. Sin embargo, tiene numerosas desventajas - que no son capaces de utilizar la nueva inicialización miembro, ni deseralization xml y etc.

Yo sugeriría algo como esto:

public class IssuerRecord 
    { 
     public string PropA { get; set; } 
     public IList<IssuerRecord> Subrecords { get; set; } 
    } 

    public class ImmutableIssuerRecord 
    { 
     public ImmutableIssuerRecord(IssuerRecord record) 
     { 
      PropA = record.PropA; 
      Subrecords = record.Subrecords.Select(r => new ImmutableIssuerRecord(r)); 
     } 

     public string PropA { get; private set; } 
     // lacks Count and this[int] but it's IReadOnlyList<T> is coming in 4.5. 
     public IEnumerable<ImmutableIssuerRecord> Subrecords { get; private set; } 

     // you may want to get a mutable copy again at some point. 
     public IssuerRecord GetMutableCopy() 
     { 
      var copy = new IssuerRecord 
          { 
           PropA = PropA, 
           Subrecords = new List<IssuerRecord>(Subrecords.Select(r => r.GetMutableCopy())) 
          }; 
      return copy; 
     } 
    } 

IssuerRecord aquí es mucho más descriptivo y útil. Cuando lo pasa en otro lugar, puede crear fácilmente una versión inmutable. El código que funciona en inmutables debe tener lógica de solo lectura, por lo que no debería importar si es del mismo tipo que IssuerRecord. Creo una copia de cada campo en lugar de simplemente envolver el objeto porque aún puede cambiarse en otro lugar, pero puede no ser necesario especialmente para llamadas de sincronización secuenciales. Sin embargo, es más seguro almacenar una copia completa inmutable en alguna colección "para más tarde". Sin embargo, puede ser un envoltorio para las aplicaciones cuando desee que algún código prohíba las modificaciones pero aún así tenga la capacidad de recibir actualizaciones del estado del objeto.

var record = new IssuerRecord { PropA = "aa" }; 
if(!Verify(new ImmutableIssuerRecord(record))) return false; 

si se piensa en términos de C++, se puede ver ImmutableIssuerRecords como "IssuerRecord const". Sin embargo, debe tener cuidado extra para proteger objetos que son propiedad de su objeto inmutable, por eso sugiero crear una copia para todos los niños (ejemplo de Subrecords).

ImmutableIssuerRecord.Subrecors es IEnumerable aquí y carece de Count y esto [], pero IReadOnlyList viene en 4.5 y puede copiarlo de docs si lo desea (y facilitar la migración posterior).

hay otros enfoques, así como congelar:

public class IssuerRecord 
{ 
    private bool isFrozen = false; 

    private string propA; 
    public string PropA 
    { 
     get { return propA; } 
     set 
     { 
      if(isFrozen) throw new NotSupportedOperationException(); 
      propA = value; 
     } 
    } 

    public void Freeze() { isFrozen = true; } 
} 

código que hace menos legible de nuevo, y no proporciona protección en tiempo de compilación. pero puedes crear objetos como normales y luego congelarlos cuando estén listos.

El patrón de generador también es algo a tener en cuenta, pero agrega demasiado código de "servicio" desde mi punto de vista.

+0

Puede usar deserialización XML muy bien. Solo necesita usar DataContractSerializer en lugar de XmlSerializer. Eso está bien para mi. –

+0

Sí, esta fue mi segunda sugerencia, es solo que esto hace que su aplicación se vea como una máquina Rube Goldberg. –