Basculer contre le polymorphisme lors de l'utilisation du modèle et de la vue

12

Je ne peux pas trouver une meilleure solution à mon problème. J'ai un contrôleur de vue qui présente une liste d'éléments. Ces éléments sont des modèles qui peuvent être une instance de B, C, D, etc. et hériter de A. Donc, dans ce contrôleur de vue, chaque élément doit aller à un écran différent de l'application et transmettre des données lorsque l'utilisateur sélectionne l'un d'entre eux . Les deux alternatives qui me viennent à l'esprit sont (veuillez ignorer la syntaxe, ce n'est pas un langage spécifique)

1) interrupteur (je sais que ça craint)

//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) polymorphisme

//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);
}

Mon problème avec la solution 2 est que, puisque B, C, D, etc. sont des modèles, ils ne devraient pas connaître les éléments liés à la vue. Ou devraient-ils dans ce cas?

Raphael Oliveira
la source

Réponses:

6

Je pense que peut-être une mise en œuvre du modèle de visiteur serait utile ici. La classe B, C et D devrait être "visitée" pour déterminer le type de vue, mais n'aurait pas besoin de connaître quoi que ce soit sur les vues. Le ViewFactory (ci-dessous) visiterait l'élément et utiliserait le polymorphisme pour déterminer la vue correcte à construire. Aucune instruction switch. Pas de questions sur les modèles internes pour décider quoi construire. L'interface visiteur utilise le polymorphisme pour sélectionner le setter correct pour la vue. Le passeur peut transmettre l'élément au constructeur du type de vue spécifique (X ou Y ou Z) et cette vue peut ensuite remplir ses champs à partir de l'élément.

   //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
la source
1
Si l'implémentation d'accepter n'est pas "theViewFactoryVisitor.setViewFor (this);" Désolé si je suis stupide!
Ryan
@Ryan Good catch. Cette erreur est là depuis 3 ans!
Chuck Krutsinger
1

Plus un commentaire qu'une réponse, mais je pense que c'est un coup sec. Soit la vue doit tout savoir sur le modèle afin qu'il puisse choisir l'écran (switch) ou le modèle doit tout savoir sur la vue donc il peut choisir l'écran (polymorphisme). Je pense que vous devez choisir ce que vous pensez être le plus simple au fil du temps; il n'y a pas de bonne réponse à la question. (J'espère que quelqu'un peut me prouver le contraire.) Je penche pour le polymorphisme, moi-même.

Je rencontre un peu ce problème. Le cas le plus ennuyeux était une classe Wanderer, dont les instances erraient sur une carte. Pour le dessiner, soit l'affichage devait connaître Wanderer, soit Wanderer devait connaître l'affichage. Le problème était qu'il y avait deux écrans (avec d'autres à venir). Le nombre de sous-classes Wanderer étant important et croissant, j'ai mis le code de dessin dans les sous-classes Wanderer. Cela signifiait que chaque grande classe avait exactement une méthode qui devait connaître Graphics2D et exactement une méthode qui devait connaître Java3D. Laid.

J'ai fini par diviser la classe, me donnant deux structures de classe parallèles. La classe Wanderer a été libérée de la connaissance des graphiques, mais la classe DrawWanderer avait encore besoin d'en savoir plus sur Wanderer que ce qui était décent et elle devait en savoir plus sur deux (et peut-être plus) environnements graphiques complètement différents (Vues). (Je suppose que cette idée de diviser la classe pourrait être une sorte de réponse, mais elle ne fait que contenir un peu le problème.)

Je pense que c'est un problème très général et fondamental de la conception orientée objet.

RalphChapin
la source
0

Je pense qu'aller avec le commutateur est une meilleure option que d'aller avec le polymorphisme dans ce cas.

C'est une chose assez simple à faire, donc je ne pense pas que cela doive être trop compliqué en utilisant le polymorphisme.

Je voudrais monnayer dans cet article de blog . Les instructions Switch ne sont pas nécessairement laides tant que vous les utilisez correctement. Et dans votre cas, des modèles abstraits comme celui à utiliser dans un contrôleur peuvent être exagérés et peuvent produire des résultats indésirables. Comme violer le SRP.

Maru
la source
Je vois ce que tu veux dire. Eh bien, je ne pense pas que le polymorphisme soit trop compliqué. Et la classe A dans mon cas n'est pas abstraite, elle est en fait utilisée. Merci pour vos réflexions, même si j'attends toujours une meilleure solution et plus encline à l'approche du polymorphisme.
Raphael Oliveira
1
pas de soucis, juste donner mes 2 cents sur la question. Bien que pour résoudre votre problème lié à la nécessité d'insérer une logique de vue dans vos modèles, vous pouvez toujours les envelopper avec des décorateurs afin que vos modèles restent libres de toute logique de vue. Ensuite, vous pouvez utiliser le polymorphisme sur les classes décoratrices au lieu du modèle.
Maru
0

Mon problème avec la solution 2 est que, puisque B, C, D, etc. sont des modèles, ils ne devraient pas connaître les éléments liés à la vue.

Je partage cette préoccupation. Je suis également un peu inquiet du fait que les objets qui se trouvent dans une zone de liste déroulante aient un comportement. Je ne suis pas sûr que ce soit une "mauvaise chose" ne l'ayant jamais fait, cela me semble juste être un choix contre nature.

En outre, cela ne semble pas être le cas Aet ses sous-classes sont le type avec lequel vous avez un polymorphisme intéressant. Le type intéressant est en fait Screen. Dans cet exemple, Aest juste une classe qui contient des informations pour informer la Screencréation.

Si vous faites en sorte que la zone de liste déroulante contienne une liste de tous les a.typeretours, une instruction switch semble plus naturelle. Cependant, au lieu de le mettre directement dans le gestionnaire d'événements click, je le mettrais dans un fichier ScreenFactory. Vous avez alors:

//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...        
}

Cela vous permet de tester le comportement de création d'écran et de bien extraire certaines fonctionnalités de votre interface utilisateur. Il garde votre couche View intacte. Peut-être que cela simplifie votre conception, si cela signifie Aque les sous-classes peuvent être regroupées dans le typedrapeau qu'elles contiennent.

tallseth
la source
Merci pour la réponse tallseth. Malheureusement, les modèles contiennent beaucoup d'informations, pas seulement leur type ou le contrôleur de vue de destination. De plus, bien que je ne l'ai pas mentionné, les ScreenX, ScreenY etc. doivent recevoir des informations sur B, C, D sur la construction, donc je ne peux pas utiliser une usine en passant uniquement le type, j'ai besoin de passer le modèle lui-même.
Raphael Oliveira