2010-11-30 9 views
8

Últimamente he estado dando cuenta de los beneficios de (algunos dirían uso excesivo de) los objetos inmutables de reducir drásticamente en temas de lectura-escritura de dependencia en mi modelo de objetos y sus condiciones resultantes y los efectos secundarios, en última instancia, a hacer el código más fácil de administrar (tipo de funcional-programación-esque).¿La mejor manera de separar preocupaciones de lectura y escritura usando interfaces?

Esta práctica me ha llevado a crear objetos de sólo lectura que se proporcionan los valores en la creación de tiempo/construcción y después de hacer disponibles sólo captadores públicas para las llamadas externas para acceder a las propiedades con. Los instaladores protegidos, internos y privados permiten mantener el control interno sobre la escritura en el modelo de objetos.

Al crear las interfaces al hacer una API sobre mi modelo de objeto, he comenzado a considerar los mismos problemas sobre la inmutabilidad. Por ejemplo, al proporcionar solo getters públicos en mis interfaces, y dejar que los implementadores decidan qué setters y cómo manejar ese aspecto.

Un ejemplo de una interfaz de "sólo lectura" para la aplicación que estoy hablando es de este objeto de valor (sólo para demostración):

public interface IValuableItem { 
    decimal Amount {get;} 
    string Currency {get;} 
} 

Sin embargo llegué a preguntarse cómo debería proporcionar una interfaz complementaria que permite escribir (y si debería), y no combinar esas operaciones dentro de la misma interfaz para no "manchar" su inmutabilidad.

Las siguientes ideas han venido a la mente, justo al lado de la parte superior de mi cabeza. Sin proporcionar lo que creo que son ventajas y desventajas para cada uno, ¿cuál crees que es el mejor enfoque? ¿Existe una metodología de codificación común en la industria para gestionar este concepto?

// companion writer 
public interface IValuableModifier { 
    decimal Amount {set;} 
    string Currency {set;} 
} 

o

// explicit methods to enforce importance of or deviance in the programming 
public interface IValuableModifier { 
    void SetAmount(decimal val); 
    void SetCurrency(string cur); 
} 

o

// companion writer that inherits the original interface 
public interface IValuableModifier : IValuableItem { //... 

o

// Let a concrete class choose one and/or the other. 
class Concrete : IValuableModifer, IValuableItem { //... 

o

etc. ..
¿Qué más me puede ayudar a imbuir a escribir en mi modelo de programación de otro modo inmutable y guardo moderadamente flexible o al menos para separar las preocupaciones para un mejor control sobre ella?

+0

Una clase que tiene una propiedad 'public' o' protected' que proporciona solo un acceso 'set' provocará una advertencia de Code Analysis [CA1044: Las propiedades no deben ser solo de escritura] (https://msdn.microsoft.com/ en-us/library/ms182165.aspx). – DavidRR

Respuesta

9

Creo que podría utilizar una variante de sus ideas, algo como esto:

public interface IValuableItem 
{ 
    decimal Amount { get; } 
    string Currency { get; } 
} 

public interface IMutableValuable : IValuableItem 
{ 
    new decimal Amount { set; get; } 
    new string Currency { set; get; } 
} 

class Item : IMutableValuable 
{ 
    public decimal Amount { get; set; } 
    public string Currency { get; set; } 
} 

De esta manera su interfaz mutable tiene captadores y definidores completos (no creo que tiene sentido tener una interfaz que tiene setters pero no getters), pero cualquier objeto que la implemente también tendrá una versión inmutable de la interfaz que puede usar para cualquier código funcional puro.

+0

+1: * No creo que tenga sentido tener una interfaz que tenga setters pero no getters * - a menos que tenga una justificación válida para la misma. –

+0

@ KMån - Estoy de acuerdo. He intentado algunas veces tener solo interfaces setter, pero casi siempre necesitas un poco de "habilidad para obtener" en alguna parte, así que no vale la pena. Podrías crear 'IMutableValuable' arriba al heredar interfaces mutables e inmutables, pero es YAGNI. – Kit

+0

@ KMån: eso puede estar justificado cuando se quiere aplicar una semántica funcional, evitando los efectos secundarios durante la evaluación, como asegurar que la evaluación compleja de un objeto no depende de los estados del objeto durante su propia evaluación. En tal caso, puede justificarse proporcionar una interfaz de solo escritura, sin acceso de lectura. Sin embargo, no es fácil diseñarlo correctamente, especialmente si se quieren especificar correctamente los métodos, lo que generalmente requiere una referencia a los métodos de acceso de lectura. – Hibou57

1

Creo que la combinación de su tercera y cuarta opción es una mejor manera de poner en práctica mutables & tipos inmutables.

Public interface ImmutableItem { 
    decimal Amount {get;} 
    string Currency {get;} 
} 

Public interface MutableItem: ImmutableItem { 
    decimal Amount {set;} 
    string Currency {set;} 
} 

class Concrete : ImmutableItem { 
    //Only getters 
} 


class Concrete : MutableItem { 
    //Both getters & setters 
} 

Ésta es limpio y se dejó que las clases concretas para decidir qué tipo de mutabilidad que se quiere exponer al mundo exterior.

+2

Si un artículo se llama a sí mismo un artículo inmutable, los consumidores deben poder asumir con seguridad que no cambiará. Dado que un elemento MutableItem, como se define, se puede convertir en un ImmutableItem, esa suposición no se cumple. Es mejor tener un ReadonlyItem, que es heredado tanto por ImmutableItem como por MutableItem. – supercat

3

Si sus objetos deben ser inmutables (y diseña su aplicación en torno al concepto de datos inmutables) entonces los objetos realmente DEBEN permanecer inmutables.

El método canónico para la modificación de datos en escenarios inmutables es crear nuevos objetos, así que yo sugeriría algo como esto:

public interface IValuableItem<T> 
{ 
    decimal Amount { get; } 
    string Currency { get; } 
    T CreateCopy(decimal amount, string currency); 
} 

public class SomeImmutableObject : IValuableItem<SomeImmutableObject> 
{ 
    public decimal Amount { get; private set; } 
    public string Currency { get; private set; } 

    public SomeImmutableObject(decimal amount, string currency) 
    { 
     Amount = amount; 
     Currency = currency; 
    } 

    public SomeImmutableObject CreateCopy(decimal amount, string currency) 
    { 
     return new SomeImmutableObject(amount, currency); 
    } 
} 

SomeImmutableObject obj = new SomeImmutableObject(123.33m, "GBP"); 
SomeImmutableObject newObj = obj.CreateCopy(120m, obj.Currency); 
2

considerar el uso de un Builder: objetos Builder crea instancias de inmutables del objeto central. .NET Strings es así: el objeto de cadena es inmutable, y hay una clase StringBuilder para la construcción eficiente de objetos de cadena. (string + string + string es mucho menos eficiente que usar un StringBuilder para hacer lo mismo)

Tenga en cuenta también que los objetos de compilación existen únicamente para construir el objeto de destino - los constructores no son instancias del objeto de destino/no implementan el objetivo interactuar ellos mismos.

Vale la pena hacer que su sistema se ejecute en objetos inmutables, ya que la inmutabilidad elimina muchos dolores de cabeza en situaciones de enhebrado/simultaneidad/ejecución paralela, así como escenarios de caché de datos/control de versiones de datos.

4

Debe tener interfaces separadas para ReadableFoo, ImmutableFoo y MutableFoo. Los dos últimos deberían heredar de la primera. ReadableFoo debe contener un método "AsImmutable" que devolverá un Foo que se garantiza que es inmutable (una instancia inmutable debería devolverse, una instancia mutable debería devolver una nueva instancia inmutable que contiene sus datos), y probablemente un miembro "AsNewMutable" (que creará una nueva instancia mutable que contenga los mismos datos, ya sea que el original sea mutable o no).

Ninguna clase debe implementar ImmutableFoo y MutableFoo.

+0

Encuentro esta respuesta interesante también.Usted está diciendo 'debería' - ¿se basa esto en una práctica general o una especificación sobre la que puedo leer más? –

+0

@John K: si un objeto dice ser inmutable, le promete a cualquiera que lo use que nunca cambiará. Si un objeto afirma ser mutable, promete que cualquiera que lo use podrá cambiarlo y (a menos que se lo cambie) el cambio se reflejará la próxima vez que se lea el objeto. Supongo que se podría tener una clase "ModelTFordColor" que fuera inmutable y mutable (puedes configurarla cuando quieras, al color que quieras, siempre que sea negro, y su color será el que establezcas) pero no puedo " Pienso en cualquier escenario útil para que una clase sea mutable e inmutable. – supercat

+1

@John K: No diré que ninguna clase "puede" implementar tanto ImmutableFoo como MutableFoo (ya que nada impide que una clase lo haga), excepto en casos triviales donde un mutador no podría tener ningún efecto real, cualquier clase que implementa ambas interfaces violará el contrato de al menos uno de ellos. – supercat

Cuestiones relacionadas