Tengo una pregunta de "mejores prácticas" sobre OOP en C # (pero se aplica a todos los idiomas).
Considere tener una clase de biblioteca con un objeto que se expondrá al público, digamos a través del descriptor de acceso de propiedad, pero no queremos que el público (las personas que usan esta clase de biblioteca) lo modifiquen.
class A
{
// Note: List is just example, I am interested in objects in general.
private List<string> _items = new List<string>() { "hello" }
public List<string> Items
{
get
{
// Option A (not read-only), can be modified from outside:
return _items;
// Option B (sort-of read-only):
return new List<string>( _items );
// Option C, must change return type to ReadOnlyCollection<string>
return new ReadOnlyCollection<string>( _items );
}
}
}
Obviamente, el mejor enfoque es la "opción C", pero muy pocos objetos tienen la variante ReadOnly (y ciertamente ninguna clase definida por el usuario la tiene).
Si fuera el usuario de la clase A, ¿esperaría cambios en
List<string> someList = ( new A() ).Items;
propagarse al objeto original (A)? ¿O está bien devolver un clon siempre que se haya escrito así en comentarios / documentación? Creo que este enfoque de clonación podría conducir a errores bastante difíciles de rastrear.
Recuerdo que en C ++ podríamos devolver objetos const y solo podría llamar a los métodos marcados como const en él. Supongo que no hay tal característica / patrón en C #? Si no, ¿por qué no lo incluirían? Creo que se llama corrección constante.
Pero, de nuevo, mi pregunta principal es sobre "qué esperarías" o cómo manejar la Opción A vs B.
fuente
Respuestas:
Además de la respuesta de @ Jesse, que es la mejor solución para las colecciones: la idea general es diseñar su interfaz pública de A de manera que solo regrese
tipos de valor (como int, double, struct)
objetos inmutables (como "cadena" o clases definidas por el usuario que ofrecen solo métodos de solo lectura, al igual que su clase A)
(solo si debe hacerlo): copias de objetos, como lo demuestra su "Opción B"
IEnumerable<immutable_type_here>
es inmutable por sí mismo, por lo que este es solo un caso especial de lo anterior.fuente
También tenga en cuenta que debe programar en interfaces y, en general, lo que desea devolver de esa propiedad es un
IEnumerable<string>
. AList<string>
es un poco un no-no porque generalmente es mutable y voy a ir tan lejos como para decir que,ReadOnlyCollection<string>
como implementador,ICollection<string>
siente que viola el Principio de sustitución de Liskov.fuente
IEnumerable<string>
es exactamente lo que uno quiere aquí.IReadOnlyList<T>
.IList<T>
)Encontré un problema similar hace un tiempo, y mi solución fue abajo, pero realmente solo funciona si controlas la biblioteca también:
Comience con su clase
A
Luego deriva una interfaz de esto con solo la parte de obtención de las propiedades (y cualquier método de lectura) y haz que A implemente eso
Luego, por supuesto, cuando desee utilizar la versión de solo lectura, un simple lanzamiento es suficiente. Esto es exactamente lo que su solución C es, realmente, pero pensé en ofrecer el método con el que lo logré
fuente
Para cualquiera que encuentre esta pregunta y aún esté interesado en la respuesta, ahora hay otra opción que está exponiendo
ImmutableArray<T>
. Estos son algunos puntos por los que creo que es mejorIEnumerable<T>
oReadOnlyCollection<T>
:IEnumerable<T>
haceIEnumerable
oIReadOnlyCollect
no puede asumir que nunca cambia (en otras palabras, no hay garantía de que los elementos de una colección no hayan sido modificados mientras la referencia a esa colección permanece sin cambios)Además, si APi necesita notificar al cliente sobre los cambios en la colección, expondría un evento como miembro separado.
Tenga en cuenta que no digo que
IEnumerable<T>
sea malo en general. Todavía lo usaría al pasar la colección como argumento o al devolver la colección inicializada perezosamente (en este caso particular, tiene sentido ocultar el recuento de elementos porque aún no se conoce).fuente