Dependencia de clase circular

12

¿Es un mal diseño tener 2 clases que se necesitan mutuamente?

Estoy escribiendo un pequeño juego en el que tengo una GameEngineclase que tiene algunos GameStateobjetos. Para acceder a varios métodos de representación, estos GameStateobjetos también necesitan conocer la GameEngineclase, por lo que es una dependencia circular.

¿Llamarías a este mal diseño? Solo pregunto, porque no estoy muy seguro y en este momento todavía puedo refactorizar estas cosas.

shad0w
fuente
2
Me sorprendería mucho si la respuesta fuera afirmativa.
doppelgreener
Como hay dos preguntas, que son lógicamente disjuntas ... la respuesta es sí a una de ellas. ¿Por qué quieres diseñar un estado que realmente haga algo? debe tener un administrador / controlador para verificar el estado y realizar la acción. Es un poco confuso dejar que un tipo de objeto de estado asuma las responsabilidades de otra cosa. Si quieres hacerlo, C ++ es tu amigo .
Teodron
Mis clases de GameState son cosas como la introducción, el juego real, un menú, etc. GameEngine almacena estos estados en una pila, por lo que puedo pausar un estado y abrir un menú, o reproducir una escena, ... Las clases de GameState necesitan conocer GameEngine, porque el motor crea la ventana y tiene el contexto de representación .
shad0w
55
Recuerde, todas estas reglas apuntan rápido. Rápido para correr, rápido para crear, rápido para mantener. Algunas veces estas facetas están en desacuerdo entre sí y usted necesita hacer un análisis de costo-beneficio para decidir cómo proceder. Si lo piensas demasiado, y haces una llamada instintiva, todavía lo estás haciendo mejor que el 90% de los desarrolladores. No hay un monstruo oculto que te vaya a matar si lo haces mal, él es más un operador oculto de peaje.
DampeS8N

Respuestas:

0

No es un mal diseño por naturaleza, pero puede salirse fácilmente de control. De hecho, está utilizando ese diseño en sus códigos cotidianos. Por ejemplo, vector sabe que es el primer iterador y los iteradores tienen un puntero a su contenedor.

Ahora en su caso, es mucho mejor tener dos clases separadas para GameEnigne y GameState. Dado que básicamente esos dos están haciendo cosas diferentes, y más tarde puede definir muchas clases que heredan GameState (como un GameState para cada escena en su juego). Y nadie puede negar su necesidad de tener acceso el uno al otro. Básicamente GameEngine está ejecutando gamestates, por lo que debería tener un puntero hacia ellos. Y GameState está utilizando recursos definidos en GameEngine (como renderizado, administrador de física, etc.).

Tampoco puedes y no debes combinar esas dos clases entre sí, ya que están haciendo cosas diferentes por naturaleza y la combinación dará como resultado una clase muy grande que a nadie le gusta.

Hasta ahora sabemos que necesitamos dependencia circular en nuestro diseño. Hay varias formas de crear eso de forma segura:

  1. Como sugirió, podemos poner un puntero de cada uno de ellos en otro, es la solución más simple, pero podría salirse fácilmente de control.
  2. También puede usar el diseño singleton / multiton, con este método debe definir su clase GameEngine como singleton y cada uno de esos GameStates como multiton. Aunque muchos desarrolladores consideran que Singleton y Multiton son AntiPattern, prefiero este diseño.
  3. Puede usar variables globales, oye, son básicamente lo mismo que Singleton / multiton, pero tienen una ligera diferencia en el hecho de que no limitan el programador no para crear instancias a voluntad.

Para concluir su respuesta, puede usar cualquiera de esos tres métodos, pero debe tener cuidado de no usar ninguno de ellos, ya que, como dije, todos esos diseños son realmente peligrosos y podrían resultar fácilmente en un código que no se puede mantener.

Ali1S232
fuente
6

¿Es un mal diseño tener 2 clases que se necesitan mutuamente?

Es un poco un olor a código , pero uno puede irse con él. Si esa es la forma más fácil y rápida de poner en marcha su juego, hágalo. Pero tenga esto en cuenta porque hay una buena posibilidad de que tenga que refactorizarlo en algún momento.

La cuestión con C ++ es que las dependencias circulares no se compilarán tan fácilmente , por lo que podría ser una mejor idea deshacerse de ellas en lugar de perder tiempo arreglando su compilación.

Vea esta pregunta en SO para algunas opiniones más.

¿Llamarías [mi diseño] mal diseño?

No, sigue siendo mejor que poner todo en una clase.

No es tan bueno, pero en realidad está bastante cerca de la mayoría de las implementaciones que he visto. Por lo general, tendrías una clase de administrador para estados de juego ( ¡cuidado! ) Y una clase de renderizador, y es bastante común que sean singletons. Entonces, la dependencia circular está "oculta", pero está potencialmente allí.

Además, como se te dijo en los comentarios, es un poco extraño que las clases de estado del juego realicen algún tipo de representación. Solo deben contener información de estado, y el renderizado debe ser manejado por un renderizador o algún componente gráfico de los objetos del juego.

Ahora puede haber el mejor diseño. Tengo curiosidad por ver si otras respuestas traen una buena idea. Aún así, probablemente eres el que puede encontrar el mejor diseño para tu juego.

Laurent Couvidou
fuente
¿Por qué sería un olor a código? La mayoría de los objetos con relación padre-hijo necesitan verse.
Kikaimaru
44
Porque es la forma más fácil de no definir qué clase es responsable de qué. Fácilmente termina en dos clases que están tan profundamente acopladas que podría agregarse código nuevo en cualquiera de ellas, por lo que ya no están separadas conceptualmente. También es una puerta abierta para el código de espagueti: la clase A llama a la clase B para esto, pero B necesita esa información de A, etc. Así que no, no diría que un objeto hijo debe saber sobre su padre. Si es posible, es mejor si no lo hace.
Laurent Couvidou
Esa es una falacia de pendiente resbaladiza. Las clases estáticas también pueden conducir a cosas malas, pero las clases de utilidad no son código olfativo. Y realmente dudo que haya alguna forma "buena" de hacer que Scene tenga SceneNodes y SceneNode tenga referencia a Scene, por lo que no puede agregarlo a dos escenas diferentes ... ¿Y dónde se detendría? ¿Se requiere A B, B requiere C y C requiere A un olor a código?
Kikaimaru
De hecho, esto es como las clases estáticas. Maneje con cuidado, úselo solo si es necesario. Ahora estoy hablando por experiencia y no soy el único que piensa de esta manera , pero, realmente, esto es una cuestión de opinión. Mi opinión es que en el contexto dado por el OP, esto es realmente maloliente.
Laurent Couvidou
No se menciona ese caso en ese artículo de Wikipedia. Y no puedes decir que es un caso de "intimidad inapropiada" hasta que hayas visto esas clases.
Kikaimaru
6

A menudo se considera un mal diseño tener que tener 2 clases que se refieren directamente entre sí, sí. En términos prácticos, puede ser más difícil seguir el flujo de control a través del código, la propiedad de los objetos y su vida útil puede ser complicada, significa que ninguna de las clases es reutilizable sin la otra, podría significar que el flujo de control realmente debería vivir fuera de ambos de estas clases en una tercera clase 'mediadora', y así sucesivamente.

Sin embargo, es muy común que 2 objetos se refieran entre sí, y la diferencia aquí es que generalmente la relación en una dirección es más abstracta. Por ejemplo, en un sistema Modelo-Vista-Controlador, el objeto Vista puede contener una referencia al objeto Modelo, y sabrá todo sobre él, pudiendo acceder a todos sus métodos y propiedades para que la Vista pueda rellenarse con datos relevantes . El objeto Modelo puede contener una referencia a la Vista para que pueda actualizar la Vista cuando sus propios datos hayan cambiado. Pero en lugar de que el Modelo tenga una referencia de Vista, lo que haría que el Modelo dependiera de la Vista, generalmente la Vista implementa una interfaz Observable, a menudo con solo 1Update()función, y el Modelo contiene una referencia a un objeto Observable, que puede ser una Vista. Cuando el Modelo cambia, llama Update()a todos sus Observables, y la Vista se implementa Update()llamando nuevamente al Modelo y recuperando cualquier información actualizada. El beneficio aquí es que el Modelo no sabe nada sobre Vistas (¿y por qué debería hacerlo?), Y puede reutilizarse en otras situaciones, incluso aquellas sin Vistas.

Tienes una situación similar en tu juego. GameEngine normalmente sabrá sobre GameStates. Pero GameState no necesita saber todo sobre GameEngine, solo necesita acceder a ciertos métodos de representación en GameEngine. Eso debería activar una pequeña alarma en tu cabeza que dice que (a) GameEngine está tratando de hacer demasiadas cosas dentro de una clase, y / o (b) GameState no necesita todo el motor del juego, solo la parte renderizable.

Tres enfoques para resolver esto incluyen:

  • Haga que GameEngine se derive de una interfaz Renderable y pase esa referencia al GameState. Si alguna vez refactoriza la parte de representación, solo debe asegurarse de que mantenga la misma interfaz.
  • Factoriza la parte de representación en un nuevo objeto Renderer y pasa eso a GameStates en su lugar.
  • Déjalo así. Tal vez en algún momento desearás acceder a toda la funcionalidad de GameEngine desde GameState, después de todo. Pero tenga en cuenta que el software que es fácil de mantener y fácil de extender por lo general requiere que cada clase se refiera lo menos posible al exterior, razón por la cual las cosas se dividen en subobjetos e interfaces que realizan un solo y bien- La tarea definida es preferible.
Kylotan
fuente
0

En general, se considera una buena práctica tener una alta cohesión con un bajo acoplamiento. Aquí hay algunos enlaces sobre el acoplamiento y la cohesión.

acoplamiento wikipedia

cohesión de wikipedia

bajo acoplamiento, alta cohesión

stackoverflow en mejores prácticas

Tener dos clases de referencia entre sí es tener un alto acoplamiento. El marco de Google Guice tiene como objetivo lograr una alta cohesión con un bajo acoplamiento a través de medios de inyección de dependencia. Te sugiero que leas un poco más sobre el tema y luego hagas tu propia llamada dado tu contexto.

avanderw
fuente