¿Cómo evitar una gran cantidad de interfaces en la interfaz de usuario con inyección de dependencia?

8

Problema
Recientemente leí mucho sobre Singletons como malo y cómo la inyección de dependencia (que entiendo como "usar interfaces") es mejor. Cuando implementé parte de esto con devoluciones de llamada / interfaces / DI y adhiriéndome al principio de segregación de interfaz, terminé con un gran lío.

Las dependencias de un UI padre donde básicamente se combinaban todas las de sus hijos, por lo que cuanto más arriba estaba la jerarquía de un elemento de UI, más hinchado estaba su constructor.

En la parte superior de la jerarquía de la interfaz de usuario había una clase de aplicación, que contenía la información sobre la selección actual y una referencia a un modelo 3D que debe reflejar los cambios. ¡La clase de aplicación estaba implementando 8 interfaces, y esto fue solo alrededor de una quinta parte de los productos (/ interfaces) por venir!

Actualmente trabajo con un singleton que contiene la selección actual y los elementos de la interfaz de usuario que tienen una función para actualizarse. Esta función desliza hacia abajo el árbol de la interfaz de usuario y los elementos de la interfaz de usuario y luego accede al singleton de selección actual según sea necesario. El código me parece más limpio de esta manera.

Pregunta
¿Un singleton puede ser apropiado para este proyecto?
Si no, ¿hay una falla fundamental en mi pensamiento y / o implementación de DI que lo hace tan engorroso?

Información adicional sobre el proyecto
Tipo: Canasta de compras para apartamentos, con campanas y silbatos
Tamaño: 2 meses-hombre para código y
mantenimiento de la interfaz de usuario : No hay actualizaciones en ejecución, pero tal vez "versión 2.0" posterior
Entorno: Uso de C # en Unity, que usa una Entidad Sistema de componentes

En casi todos los casos, la interacción del usuario desencadena varias acciones. Por ejemplo, cuando el usuario selecciona un elemento

  • la parte de la interfaz de usuario que muestra ese elemento y su descripción debe actualizarse. Para esto, también necesita obtener información de un modelo 3D para calcular el precio.
  • más arriba en la interfaz de usuario, el precio total general debe actualizarse
  • Es necesario invocar una función correspondiente en una clase en un modelo 3D para mostrar los cambios allí.
R. Schmitz
fuente
DI no se trata solo de usar interfaces, es la capacidad de intercambiar concreciones lo que es especialmente útil en la esfera de pruebas unitarias ...
Robbie Dee
1
Además, todavía tengo que ver un problema en el que un singleton es la solución preferida. El ejemplo canónico que la gente siempre parece decir es un escritor de registros, pero incluso aquí es posible que desee escribir en varios registros diferentes (error, depuración, etc.).
Robbie Dee
@RobbieDee Sí, DI es sobre más, pero no puedo ver una situación en la que el uso de una interfaz no sea DI. Y si un singleton no es la solución preferida, que también supongo, ¿por qué la solución preferida es tan desordenada? Esa es mi pregunta principal, pero quería mantenerla abierta, ya que a veces la regla general no se aplica.
R. Schmitz
La disposición de concreción de interfaz también es una característica de los marcos de burla. También han estado en idiomas OO durante unas pocas décadas ...
Robbie Dee
No entiendo tu punto.
R. Schmitz

Respuestas:

4

Creo que la pregunta es un síntoma, no una solución.

Recientemente leí mucho sobre Singletons como malo y cómo la inyección de dependencia (que entiendo como "usar interfaces") es mejor. Cuando implementé parte de esto con callbacks / interfaces / DI y adhiriéndome al principio de segregación de interfaz, terminé con un gran lío.

Una solución buscando un problema; eso y malinterpretarlo probablemente está corrompiendo su diseño. ¿Has leído esta pregunta SO sobre DI vs Singleton, diferentes conceptos o no? Leí eso simplemente envolviendo un singleton para que el cliente no tenga que lidiar con un singleton. Es.solo.en.encapsulación.buena.buena. Creo que eso es perfecto.


cuanto más arriba en la jerarquía estaba un elemento de la interfaz de usuario, más hinchado estaba su constructor.

Primero construya los bits más pequeños, luego páselos al constructor de la cosa a la que pertenecen y pase esa cosa más grande al constructor de la próxima cosa más grande ...

Si tiene problemas de construcción complejos, use el patrón de fábrica o de construcción. La conclusión es que la construcción compleja se incorporó a su propia clase para mantener otras clases ordenadas, limpias, comprensibles, etc.


¡La clase de aplicación estaba implementando 8 interfaces, y esto fue solo alrededor de una quinta parte de los productos (/ interfaces) por venir!

Esto suena como la ausencia de capacidad extendida. Falta el diseño central y todo está repleto desde la parte superior. Debería haber más construcción ascendente, composición y herencia.

Me pregunto si su diseño es "muy pesado". Parece que estamos tratando de hacer que una clase sea todo o cualquier cosa que pueda ser. Y el hecho de que sea una clase de interfaz de usuario, no una clase de dominio empresarial, realmente me hace preguntarme sobre la separación de las preocupaciones.

Revise su diseño desde el principio y asegúrese de que exista una abstracción de producto sólida y básica sobre la que se pueda construir para hacer producciones de categorías más complejas o diferentes. Entonces es mejor que tenga colecciones personalizadas de estas cosas, de modo que tenga un lugar para poner la funcionalidad de "nivel de colección", como ese "tercer modelo" que menciona.


... modelo 3D que necesita reflejar los cambios.

Gran parte de esto puede caber en clases de colección personalizadas. También puede ser una estructura de clase independiente en sí misma debido a la profundidad y complejidad. Estas dos cosas no son mutuamente excluyentes.

Lea sobre el patrón de visitante. Es la idea de tener conjuntos completos de funcionalidades conectadas de manera abstracta a diferentes tipos.


Diseño y DI

El 90% de toda la inyección de dependencia que alguna vez harás es pasar parámetros de constructor. Eso dice el quy que escribió el libro . Diseñe bien sus clases y evite contaminar ese proceso de pensamiento con una vaga noción sobre la necesidad de usar un contenedor DI. Si lo necesita, su diseño se lo sugerirá, por así decirlo.


Concéntrese en modelar el dominio de compra de apartamentos.

Evite el enfoque del diseño de Jessica Simpson : "No sé qué significa eso, pero lo quiero".

Los siguientes son simplemente incorrectos:

  • Se supone que debo usar interfaces
  • Se supone que no debo usar un singleton
  • Necesito DI (lo que sea que sea)
  • Se supone que debo usar composición, no herencia
  • Se supone que debo evitar la herencia
  • Necesito usar patrones
radarbob
fuente
Traté de deshacerme de todos los singletons que usé y algunas de las cosas que dijiste sucedieron más o menos automáticamente. El resto también tiene mucho sentido y voy a seguir su consejo y leer sobre el patrón de visitante, etc. En general, una respuesta bien escrita, ¡gracias!
R. Schmitz
Sólo un punto, y no estoy tratando de ser un vampiro ayuda aquí, pero era especie de parte de la pregunta original: El consenso general (y que se basa en razones válidas) parece ser que está hecho no supone usa un singleton. Escribe "'Se supone que no debo usar un singleton' está mal". ¿En qué situación sería apropiado usar uno entonces?
R. Schmitz
Todo lo que digo es, "depende". Con demasiada frecuencia veo máximas tomadas literalmente.
radarbob
3

La "jerarquía de clases" es una especie de bandera roja. Hipotético: una página web con 5 widgets. La página no es un ancestro de ninguno de los widgets. Puede contener una referencia a esos widgets. Pero no es un antepasado. En lugar de la jerarquía de clases, considere usar composición. Cada uno de los 5 widgets se puede construir por sí solo, sin referencia a los otros widgets o la página superior. La página superior se construye con suficiente información para construir una página básica y diseñar los objetos de widget (Colección) que se le pasan. La página es responsable del diseño, etc., pero no de la construcción y la lógica de los widgets.

Una vez que usa composición, DI es su mejor amigo. DI le permite cambiar cada widget por cualquier versión o tipo de widget que defina en DI. La construcción de los widgets se captura en la DI y está separada de la página superior. Quizás incluso la composición de la Colección se pueda definir en su DI. La página superior realiza el diseño en función de los widgets que se le pasan, como debería. No se necesitan cambios en el constructor de la página superior. La página superior solo necesita la lógica para realizar el diseño de los widgets y pasar información a / desde el widget en función de las interfaces definidas.

En lugar de pasar a los oyentes por la cadena de composición, inyecte una colección de oyentes a los widgets que lo necesitan. Inyecte la colección en un editor para que puedan publicar en la colección. Inyecte la colección en los oyentes para que puedan agregarse a la colección. La colección atraviesa todos los objetos en la cadena de composición.

scorpdaddy
fuente
Lo sentimos, "jerarquía de clases" era una redacción incorrecta aquí; en realidad no era jerarquía de clases, sino más bien jerarquía de interfaz de usuario. Los 'widgets' no son subclases de la clase de aplicación, pero la 'página' contiene referencias a ellos para publicar actualizaciones de la interfaz de usuario. Era muy comprobable con esta estructura, pero también había mucha sobrecarga, especialmente en los constructores, simplemente transmitiendo una gran cantidad de oyentes para sus hijos (UI).
R. Schmitz
@ R.Schmitz: ¿Importaba la sobrecarga? ¿La IU era lenta y el diseño que describiste tenía la culpa?
Robert Harvey
@ R.Schmitz ¿Puedes dar más detalles sobre "transmitir muchos oyentes"? Tal vez mi experiencia con DI en C # es baja, pero algo me dice que no sería necesario (al menos en el constructor) con un diseño adecuado.
Katana314
@RobertHarvey Sin pérdida de rendimiento, solo se trata de legibilidad.
R. Schmitz
@ Katana314 Por ejemplo, cuando se agrega un elemento, el modelo 3d debe actualizarse y no pertenece a ninguna de las categorías de productos, por lo que se hace referencia en la clase de aplicación, que escucha los elementos que se agregan / eliminan / cambian. Al final, eso sería prácticamente lo mismo para cada categoría de producto. Otras partes de la interfaz de usuario también reaccionan (y escuchan), pero el mensaje debe viajar a la parte superior porque allí es donde está la referencia del modelo 3d y los datos reales (que también se actualizan).
R. Schmitz