Cambiar vs polimorfismo cuando se trata de modelo y vista

12

No puedo encontrar una mejor solución a mi problema. Tengo un controlador de vista que presenta una lista de elementos. Esos elementos son modelos que pueden ser una instancia de B, C, D, etc. y heredar de A. Por lo tanto, en ese controlador de vista, cada elemento debe ir a una pantalla diferente de la aplicación y pasar algunos datos cuando el usuario selecciona uno de ellos . Las dos alternativas que se me ocurren son (ignore la sintaxis, no es un lenguaje específico)

1) cambiar (sé que apesta)

//inside the view controller
void onClickItem(int index) {
    A a = items.get(index);

    switch(a.type) {
         case b:
             B b = (B)a;
             go to screen X;
             x.v1 = b.v1; // fill X with b data
             x.v2 = b.v2; 
         case c:
             go to screen Y;
         etc...
    }
}

2) polimorfismo

//inside the view controller
void onClickItem(int index) {
    A a = items.get(index);
    Screen s = new (a.getDestinationScreen()); //ignore the syntax
    s.v1 = a.v1;   // fill s with information about A
    s.v2 = a.v2;
    show(s);
}

//inside B
Class getDestinationScreen(void) {
    return Class(X);
}

//inside C
Class getDestinationScreen(void) {
    return Class(Y);
}

Mi problema con la solución 2 es que, dado que B, C, D, etc.son modelos, no deberían saber sobre cosas relacionadas con la vista. ¿O deberían en ese caso?

Raphael Oliveira
fuente

Respuestas:

6

Creo que quizás una implementación del patrón de visitante sería útil aquí. Las clases B, C y D necesitarían ser "visitadas" para determinar el tipo de vista, pero no necesitarían saber nada sobre las vistas. ViewFactory (a continuación) visitaría el elemento y usaría polimorfismo para determinar la vista correcta para construir. No hay declaraciones de cambio. No hay que preguntar sobre modelos internos para decidir qué construir. La interfaz de visitante utiliza polimorfismo para seleccionar la configuración correcta para la vista. El instalador puede pasar el elemento al constructor del tipo de vista específico (X o Y o Z) y esa vista puede llenar sus campos del elemento.

   //inside the view controller
   void onClickItem(int index) {
      ViewFactoryVisitable a = items.get(index);
      ViewFactory aViewFactory = new ViewFactory(
      s = aViewFactory.getViewFor(a);
      show(s);
   }

--------

//Element interface
public interface ViewFactoryVisitable
{
    public void accept(ViewFactory theViewFactory);
}

---------

public interface ViewFactoryVisitor
{
   // one for each concrete type, polymorphism will choose correct setter
   public set setViewFor(B b);
   public set setViewFor(C c);
   public set setViewFor(D d);
}

--------

// B, C, D must implement this visitable interface
class B implements ViewFactoryVisitable
{ 
   ...

   //accept the ViewFactory as a visitor
   public void accept(ViewFactoryVisitor theViewFactoryVisitor)
   {
      theViewFactoryVisitor. setViewFor(this);
   }

   ...
} 

--------

class ViewFactory implements ViewFactoryVisitor
{
   ViewFactory(ViewFactoryVisitable theItem) {
      theItem.accept(this);
   }

   private View mView = null;
   ...

   public void setViewFor(B b) {
      // construct a view x and populate with data from b
      mView = new ViewX(b); 
   }

   public void setViewFor(C c) {
      mView = new ViewY(c); 
   }

   public void setViewFor(D d) {
      mView = new ViewZ(d); 
   }

   View getView() {
      return mView;
   }

} 
Chuck Krutsinger
fuente
1
Si la implementación de accept no es "theViewFactoryVisitor.setViewFor (this);" Lo siento si estoy siendo estúpido!
Ryan
@ Ryan Buena captura. ¡Este error ha estado aquí por 3 años!
Chuck Krutsinger el
1

Más un comentario que una respuesta, pero creo que es una sacudida. O bien, la Vista tiene que saber todo sobre el Modelo para poder elegir la pantalla (interruptor) o el Modelo debe saber todo sobre la Vista para que pueda elegir la pantalla (polimorfismo). Creo que tienes que elegir lo que creas que será el más simple con el tiempo; No hay una respuesta correcta a la pregunta. (Espero que alguien pueda demostrar que estoy equivocado). Me inclino por el polimorfismo, yo mismo.

Me encuentro con este problema un poco. El caso más molesto fue una clase Wanderer, cuyas instancias deambularon por un mapa. Para dibujarlo, la pantalla necesitaba saber sobre Wanderer o Wanderer necesitaba saber sobre la pantalla. El problema era que había dos pantallas (con más en camino). Como el número de diferentes subclases de Wanderer era grande y creciente, puse el código de dibujo en las subclases de Wanderer. Eso significaba que cada clase grande tenía exactamente un método que necesitaba saber sobre Graphics2D y exactamente un método que necesitaba saber sobre Java3D. Feo.

Terminé dividiendo la clase, dándome dos estructuras de clase paralelas. La clase Wanderer se liberó de conocer los gráficos, pero la clase DrawWanderer aún necesitaba saber más sobre Wanderer de lo que era decente y necesitaba saber sobre dos (y tal vez más) entornos gráficos completamente diferentes (Vistas). (Supongo que esta idea de dividir la clase podría ser una especie de respuesta, pero todo lo que realmente hace es contener un poco el problema).

Creo que este es un problema muy general y fundamental del diseño orientado a objetos.

RalphChapin
fuente
0

Creo que ir con el interruptor es una mejor opción que ir con polimorfismo para este caso.

Es algo bastante simple de hacer, así que no creo que deba complicarse demasiado mediante el uso de polimorfismo.

Me gustaría acuñar en esta publicación de blog . Las declaraciones de cambio no son necesariamente feas siempre que las use correctamente. Y en su caso, los modelos de abstracción como ese para su uso en un controlador pueden ser excesivos y producir resultados no deseados. Como violar el SRP.

Maru
fuente
Te entiendo. Bueno, no creo que el polimorfismo sea demasiado complicado. Y la clase A en mi caso no es abstracta, de hecho se usa. Gracias por sus pensamientos, aunque todavía estoy esperando una mejor solución y más inclinado al enfoque de polimorfismo.
Raphael Oliveira
1
no se preocupe, solo le doy mis 2 centavos al respecto. Aunque para resolver su problema de tener que poner la lógica de vista en sus modelos, siempre puede envolverlos con decoradores para que sus modelos se mantengan libres de lógica de vista. Luego puede usar el polimorfismo en las clases de decorador en lugar del modelo.
Maru
0

Mi problema con la solución 2 es que, dado que B, C, D, etc.son modelos, no deberían saber sobre cosas relacionadas con la vista.

Estoy de acuerdo con esta preocupación. También me preocupa un poco que los objetos que se encuentran en un cuadro combinado tengan un comportamiento. No estoy seguro de que sea una "cosa mala" que nunca lo haya hecho, simplemente me parece una elección antinatural.

Además, no parece Ay sus subclases son del tipo con el que tienes un polimorfismo interesante. El tipo interesante es en realidad Screen. En este ejemplo, Aes solo una clase que contiene información para informar la Screencreación.

Si hace que el cuadro combinado contenga una lista de lo que sea que a.typedevuelva, una declaración de cambio parece más natural. Sin embargo, en lugar de ponerlo en el controlador de eventos de clic, lo pondría en un ScreenFactory. Entonces tiene:

//inside the view controller
void onClickItem(int index) {
    A a = items.get(index);

    s = _screenFactory.GetScreen(a);
    show(s);
    }
}

//inside a ScreenFactory implementation
internal Screen GetScreen(A typeIndicator)
{
switch(a.type) {
     case b:
         return new ScreenX();
     case c:
         return new ScreenY();
     etc...        
}

Esto le permite probar el comportamiento de creación de pantalla y extrae de forma agradable algunas funciones de su interfaz de usuario. Mantiene sus capas de Vista intactas. Quizás simplifique su diseño, si eso significa, Ay las subclases pueden colapsarse en la typebandera que contienen.

tallseth
fuente
Gracias por la respuesta tallseth. Desafortunadamente, los modelos contienen mucha información, no solo su tipo o el controlador de vista de destino. Además, aunque no lo mencioné, ScreenX, ScreenY, etc. necesitan recibir información sobre B, C, D en la construcción, por lo que no puedo usar una fábrica que solo pase el tipo, necesito pasar el modelo en sí.
Raphael Oliveira