franciscogazitua / IIC2113-2024

Consultas sobre el proyecto en las Issues
2 stars 0 forks source link

[E4] Interfaz gráfica #111

Open camibennett opened 1 week ago

camibennett commented 1 week ago

Hola! Estoy intentando seguir el tutorial para incorporar la interfaz gráfica en el proyecto y no me queda claro a qué corresponde la clase FireEmblemView de la que se habla (es la clase View?). En mi caso tengo muchas clases de vista y no estoy entendiendo bien como hacer para conectarlas con la interfaz. Debo modificar cada clase de vista?

Quedo atenta :)

RodrigoToroIcarte commented 1 week ago

El ejemplo asume que la vista tiene una sola clase de entrada, llamada FireEmblemView. Esa clase recibe el View del código base y tiene métodos para hacer "todo" lo que debe hacer la vista: Desde un método para elegir el archivo de los equipos hasta uno para anunciar que ninguna unidad logró hacer un follow up.

Si uno hace esto sin cuidado, corre el riesgo de que FireEmblemView sea una clase gigante con muchas responsabilidades. Por lo mismo, uno igual puede tener vistas dedicadas a cada cosa. En el ejemplo, el TeamSelectionView tiene la lógica para elegir equipos y desde el FireEmblemView simplemente se llama a sus métodos.

Tener una única vista de entrada (en este caso, FireEmblemView) tiene la ventaja de que se vuelve trivial cambiar de la vista consola a la vista GUI. En el ejemplo, basta con crear la interfaz IFireEmblemView. Así el controlador funciona con cualquier vista que implemente IFireEmblemView. Luego podemos crear una vista para la consola y otra para la interfaz gráfica. Finalmente, cuando parte el programa, podemos decidir si al controlador (la clase Game) le damos la vista consola o la vista GUI -- tal como muestra el ejemplo del enunciado.

Si te entendí bien, creo que el problema que estás teniendo es que en tu caso tienes varias vistas. Me imagino que tu programa sigue usando nuestro View, que es entregado a todos tus controladores. Luego, creas de forma local el tipo de vista que necesitas en cada momento. Por ejemplo, si quieres anunciar los bonus, creas un BonusView bonusView = new BonusView(_view) para luego hacer bonusView.Announce(...). Si este es el caso te será un poco más difícil hacer el cambio de una vista a otra.

Básicamente por cada una de tus vistas tendrás que crear una interfaz y luego dos vistas que hereden de ella. Por ejemplo, crear una IBonusView que sea implementada por un ConsoleBonusView y un GUIBonusView. Ambas vistas tendrá el mismo método Announce(...) pero la vista consola lo muestra en consola mientras que la vista GUI lo muestra en la interfaz gráfica.[^1]

El problema es que después en todas las clases de tu controlador tendrás que decidir entre crear las vistas de tipo consola o las vistas de tipo GUI. La forma obvia de hacer esto es tener un flag en todas las clases de tu controlador que diga si se está usando el modo consola o el modo GUI. Luego tener if/else que creen la vista apropiada (y esos if/else estarán copiados y pegados en varias partes). Obviamente, esta solución es mala porque rompe el Open-Closed principle, mucho código duplicado, etc.

Una solución más elegante es usar un AbstractFactory. Puedes tener una factory de vistas. La factory tendrá métodos para crea una vista de bonus, crea una vista de neutralizadores, crea una vista para elegir equipos, etc. Luego de esa factory hereda un factory que siempre crea vistas de tipo consola y otra que siempre cree vistas de tipo GUI. Así, al iniciar tu programa, al Game le das la factory que debe utilizar para crear las vistas y ya está.

... obviamente hay algunos detalles que no discutí pero son fáciles de resolver ;)

¿Esto responde tu pregunta?

[^1]: Este ejemplo es super fome porque en la GUI no se anuncian los bonus... pero en otras vistas, como la TeamSelectionView, la vista GUI sí hará cosas :P

camibennett commented 1 week ago

Hola, creo que entendí un poco más. Me queda la duda por ejemplo con las vistas de mostrar efectos. En mi caso tengo una vista por cada tipo de efecto que se muestra y una clase EffectsView que inicializa las otras vistas con un switch que va llamando a cada vista en particular. Por lo que entiendo, la interfaz no muestra los efectos, por lo que no sería necesario hacer la interfaz para cada vista de efecto, o no entendí bien?

RodrigoToroIcarte commented 1 week ago

Sí. Tienes que crear una vista para la interfaz que, a diferencia de la vista consola, no hace nada cuando se llaman a sus métodos.

rmezaa commented 1 week ago

Hola profe! No me queda tan claro la implementación de la interfaz a nivel de sus métodos. Estaríamos mostrando menos cosas que en la ConsoleView? Por lo que entendí, hay que implementar en la interfaz gráfica solo los métodos que están siendo usados en el program.cs del ejemplo de uso de gui, en el método RunGuiView(), o me equivoco?

Y usar los métodos de FireEmblemWindow en FireEmblemGuiView, algo así?

vpaivag commented 1 week ago

Hola. A mí me quedo una duda respecto a los tests. Que siguiendo el ejemplo del enunciado, se cambia el tipo de _view de View a IFireEmblemView en la clase Game. Pero al hacer esto rompo el proyecto de tests porque ahí se usan objetos de View. ¿Me salté algún paso? ¿O debería editar también la View para los test?

RodrigoToroIcarte commented 1 week ago

No es necesario editar los tests. Si te fijas bien en el ejemplo, Game termina con dos constructores:

Screenshot from 2024-06-23 22-22-12

El primer constructor es usado por los tests y la vista en consola. El segundo constructor es usado para la interfaz gráfica. Si hace eso, no debería ser necesario editar los test cases.

RodrigoToroIcarte commented 1 week ago

Hola profe! No me queda tan claro la implementación de la interfaz a nivel de sus métodos. Estaríamos mostrando menos cosas que en la ConsoleView? Por lo que entendí, hay que implementar en la interfaz gráfica solo los métodos que están siendo usados en el program.cs del ejemplo de uso de gui, en el método RunGuiView(), o me equivoco?

Y usar los métodos de FireEmblemWindow en FireEmblemGuiView, algo así?

Sí. Hay métodos de la vista en consola que no harán nada en la vista GUI. Por ejemplo, en la vista GUI no se anuncian los bonus. Pero sí se muestran los stats + bonus/penalties/neutralizadores durante la batalla.

Dentro de FireEmblemGuiView tendrás que usar el FireEmblemWindow para poder mostrar la ventana y hacer que cosas pasen en ella :)

RodrigoToroIcarte commented 1 week ago

Tienes un bug. El llamado debe ser: _window.Start(game.Play). No estás llamando al método Play(). Estás dando el método Play como argumento a _window.Start(...). Si hacer _window.Start(game.Play()) estás dando el retorno de Play() como input (y su retorno es void).

El método _window.Start(...) recibe una función como argumento. Esa función es llamada desde dentro de _window luego de crear la ventana.

vpaivag commented 1 week ago

Tienes un bug. El llamado debe ser: _window.Start(game.Play). No estás llamando al método Play(). Estás dando el método Play como argumento a _window.Start(...). Si hacer _window.Start(game.Play()) estás dando el retorno de Play() como input (y su retorno es void).

El método _window.Start(...) recibe una función como argumento. Esa función es llamada desde dentro de _window luego de crear la ventana.

Si ahi cache jejeje. Gracias profe. Todo funciono Ok