Estamos haciendo muchas pruebas unitarias y refactorizando nuestros objetos comerciales, y parece que tengo opiniones muy diferentes sobre el diseño de la clase que otros pares.
Una clase de ejemplo de la que no soy fanático:
public class Foo
{
private string field1;
private string field2;
private string field3;
private string field4;
private string field5;
public Foo() { }
public Foo(string in1, string in2)
{
field1 = in1;
field2 = in2;
}
public Foo(string in1, string in2, string in3, string in4)
{
field1 = in1;
field2 = in2;
field3 = in3;
}
public Prop1
{ get { return field1; } }
{ set { field1 = value; } }
public Prop2
{ get { return field2; } }
{ set { field2 = value; } }
public Prop3
{ get { return field3; } }
{ set { field3 = value; } }
public Prop4
{ get { return field4; } }
{ set { field4 = value; } }
public Prop5
{ get { return field5; } }
{ set { field5 = value; } }
}
En la clase "real", no todas son cadenas, pero en algunos casos tenemos 30 campos de respaldo para propiedades completamente públicas.
Me gusta esta clase, y no sé si estoy siendo quisquilloso. Algunas cosas notables:
- Los campos de respaldo privados sin lógica en las propiedades, parecen innecesarios e hinchan la clase
- Múltiples constructores (algo bien) pero junto con
- Todas las propiedades tienen un setter público, no soy un fanático.
- Potencialmente, a las propiedades no se les asignaría un valor debido al constructor vacío, si una persona que llama no lo sabe, podría obtener algunos comportamientos no deseados y difíciles de probar.
- ¡Son demasiadas propiedades! (en el caso 30)
Me resulta mucho más difícil saber realmente en qué estado se encuentra el objeto Foo
en un momento dado, como implementador. El argumento se hizo "podría no tener la información necesaria para establecer Prop5
en el momento de la construcción del objeto. Ok, supongo que puedo entender eso, pero si ese es el caso, haga Prop5
público solo el setter, no siempre hasta 30 propiedades en una clase.
¿Estoy siendo quisquilloso y / o loco por querer una clase que sea "fácil de usar" en lugar de ser "fácil de escribir (todo público)"? Clases como las anteriores me gritan, no sé cómo se va a usar esto, así que voy a hacer todo público por si acaso .
Si no estoy siendo muy exigente, ¿cuáles son buenos argumentos para combatir este tipo de pensamiento? No soy muy bueno para articular argumentos, ya que me siento muy frustrado al tratar de expresar mi punto de vista (no intencionalmente, por supuesto).
fuente
get
oset
:-)Respuestas:
Las clases completamente públicas tienen una justificación para ciertas situaciones, así como el otro extremo, clases con un solo método público (y probablemente muchos métodos privados). Y clases con algunos métodos públicos, algunos privados también.
Todo depende del tipo de abstracción que vaya a modelar con ellos, qué capas tiene en su sistema, el grado de encapsulación que necesita en las diferentes capas y (por supuesto) a qué escuela de pensamiento llega el autor de la clase desde. Puede encontrar todos estos tipos en código SOLIDO.
Hay libros completos escritos sobre cuándo preferir qué tipo de diseño, por lo que no voy a enumerar ninguna regla aquí al respecto, el espacio en esta sección no sería suficiente. Sin embargo, si tiene un ejemplo del mundo real para una abstracción que le gusta modelar con una clase, estoy seguro de que la comunidad aquí lo ayudará a mejorar el diseño.
Para abordar sus otros puntos:
Entonces en lugar de
escribir
o
"Constructores múltiples": eso no es un problema en sí mismo. Se produce un problema cuando hay una duplicación de código innecesaria allí, como se muestra en su ejemplo, o la jerarquía de llamadas está enrevesada. Esto se puede resolver fácilmente refactorizando partes comunes en una función separada y organizando la cadena de constructor de manera unidireccional
"Potencialmente no se asignaría un valor a las propiedades debido al constructor vacío": en C #, cada tipo de datos tiene un valor predeterminado claramente definido. Si las propiedades no se inicializan explícitamente en un constructor, se les asigna este valor predeterminado. Si esto se usa intencionalmente , está perfectamente bien, por lo que un constructor vacío podría estar bien si el autor sabe lo que está haciendo.
"¡Son demasiadas propiedades! (En el caso de 30)": sí, si usted es libre de diseñar una clase de este tipo de manera ecológica, 30 son demasiadas, estoy de acuerdo. Sin embargo, no todos tenemos este lujo (¿no escribiste en el comentario a continuación es un sistema heredado?). A veces tiene que asignar registros de una base de datos, archivo o datos existentes de una API de terceros a su sistema. Entonces, para estos casos, 30 atributos pueden ser algo con lo que uno tiene que vivir.
fuente
null
¿no? así que si una de las propiedades que realmente se usa en a.ToUpper()
, pero nunca se establece un valor para ella, generaría una excepción de tiempo de ejecución. Este es otro ejemplo bien por qué los necesarios datos para la clase deben tener que ser establecido durante la construcción de objetos. No solo se lo dejo al usuario. GraciasAl decir que "los campos de respaldo privados sin lógica en las propiedades, parecen innecesarios e hinchan la clase" ya tienes un argumento decente.
No parece que varios constructores sean el problema aquí.
En cuanto a tener todas las propiedades como públicas ... Tal vez piense de esta manera, si alguna vez quisiera sincronizar el acceso a todas estas propiedades entre subprocesos, lo pasaría mal porque podría tener que escribir código de sincronización en todas partes usado. Sin embargo, si todas las propiedades estaban encerradas por getters / setters, entonces podría construir fácilmente la sincronización en la clase.
Creo que cuando dices "comportamiento difícil de probar" el argumento habla por sí mismo. Es posible que su prueba tenga que verificar si no es nula o algo así (en cada lugar donde se usa la propiedad). Realmente no sé porque no sé cómo se ve su prueba / aplicación.
Tienes demasiadas propiedades, ¿podrías intentar usar la herencia (si las propiedades son lo suficientemente similares) y luego tener una lista / matriz usando genéricos? Luego, podría escribir getters / setters para acceder a la información y simplificar la forma en que interactuará con todas las propiedades.
fuente
Como me dijiste,
Foo
es un objeto comercial. Debe encapsular algún comportamiento en los métodos de acuerdo con su dominio y sus datos deben permanecer tan encapsulados como sea posible.En el ejemplo que muestra, se
Foo
parece más a un DTO y va en contra de todos los principios de OOP (ver Modelo de dominio anémico ).Como mejoras sugeriría:
Esta podría ser una posible clase refactorizada con estos comentarios:
fuente
"Ahora podemos probar esos métodos refactorizados. Tal como está, por razones x, y, z el cliente no es verificable".
En la medida en que se pueda hacer cualquiera de los argumentos anteriores, en conjunto:
fuente