2011-04-17 2 views
0

J'ai une application C++ conçue selon un Model-View-Controller pattern classique. Le modèle est modifié via une interface de contrôleur par une source externe au moyen d'un Command pattern. Les commandes sont représentées par un objet Action (et ses dérivés).Annuler la fonctionnalité dans une conception MVC

Maintenant, je veux être en mesure d'annuler les modifications, mais mon problème est que je n'ai pas de getters dans mon contrôleur, seulement des setters. Cela semble assez logique, car il n'y a aucune raison pour que quelqu'un puisse obtenir des informations sur le modèle via le contrôleur. Ainsi, je ne peux pas laisser mes objets Action stocker l'état du Modèle, puisqu'ils n'y ont pas accès.

Comment pourrait-on résoudre ce problème? Je voudrais garder mon application aussi extensible que possible et je ne suis pas tout à fait sûr quelle option est la meilleure pour cela. Les méthodes que j'utilise jusqu'ici sont les suivantes:

  1. Mettre en place des méthodes getter dans le contrôleur. Cela semble aller à l'encontre du modèle MVC.
  2. Donner à l'action un pointeur vers une vue. L'action peut alors:
    1. Utiliser des accesseurs individuels pour obtenir l'état d'éléments spécifiques du modèle à modifier.
    2. Utilisez une méthode Memento implémentée par le visualiseur.

Peut-être qu'il ya une meilleure façon de le faire? À l'heure actuelle, pour être la meilleure option semble être 2, sous-option 1 (avec la sous-option 2, je pourrais tout à fait stocker beaucoup plus d'état que nécessaire pour annuler une action).

Remarque: Je sais qu'il existe d'autres questions sur la façon d'implémenter une action d'annulation. Cependant, les seules réponses que j'ai trouvées ont donné des suggestions pour utiliser un pattern Command ou Memento. Je sais que c'est probablement le chemin à parcourir. Ce que je demande est de savoir comment l'intégrer aussi proprement que possible dans une conception MVC. Ce qui ne me plaît pas dans le motif Memento, c'est qu'il me force à stocker un état complet. Disons que mon modèle est une matrice 1000x1000 et que ma commande est ChangeOneValueAtLocation. Pour pouvoir annuler ses modifications, l'objet ChangeOneValueAtLocation n'a besoin que de stocker la valeur précédente de l'emplacement qu'il change, mais cela ne semble pas possible avec Memento. Plus mon modèle est grand, plus ce problème devient important.

[Edit 2] Un autre problème que j'ai avec Memento dans le cas particulier de cette application: pour chaque méthode d'un objet de commande peut exécuter sur le modèle, il y a une méthode qui fait exact opposé (ou peut facilement être cajolé à faites-le). C'est pourquoi je trouverais un gâchis d'avoir à stocker tout l'état, il ne devrait pas être nécessaire de retourner une seule commande est très simple, le seul problème est d'obtenir les données pour pouvoir le faire.

De plus, je n'ai pas besoin d'être capable d'annuler une commande spécifique, seulement la plus haute de ma pile d'historique.

Répondre

2

I également un soutien la couche modèle contenant le support d'annulation. Il y a plusieurs façons de gérer cela du côté du modèle. Le premier et le plus évident est que les modèles eux-mêmes se souviennent de l'histoire des changements avec des "labels", mais cela va probablement être difficile à synchroniser pour toutes vos classes de modèles. Une autre option consiste à créer un gestionnaire d'historique qui a un concept de «transaction», ce qui entraîne la génération d'un point d'annulation, et prend un instantané de vos modèles, ou commence à enregistrer les changements (pour une utilisation réduite de la mémoire) ou les commandes d'enregistrement qui provoquent des changements de modèle, etc. Les modèles notifient le gestionnaire au changement, et finalement vous complétez la transaction (ou pas, parce que le prochain début de transaction peut être la fin du précédent). Une fois que vous avez ajouté la possibilité de revenir à un certain point, le travail sera terminé. En rendant les choses un peu plus compliquées dans cette classe de gestionnaire, vous pouvez créer un arbre d'annulation (comme celui d'emacs), donc c'est aussi une manière assez flexible de l'approcher.

La solution ci-dessus n'est pas tout à fait dans la couche de modèle, cependant. C'est une classe de support qui est pilotée à la fois par le modèle et le contrôleur. Si vous supprimez le concept de transaction, celui-ci est entièrement piloté par le modèle, mais l'implémentation du concept d'opération d'annulation peut s'avérer difficile. Si vous le modifiez pour agir comme un proxy de commande, c'est la seule entité utilisée par vos contrôleurs, et c'est clairement un modèle. À ce stade, il est trop difficile de choisir une approche plutôt qu'une autre, mais je penche pour le modèle de la «transaction». C'est assez facile à mettre en œuvre.

+0

J'ai décidé d'adopter cette approche, car elle me permet de transférer facilement des modèles et leur historique associé à des clients externes. Mettre l'histoire dans un contrôleur rendrait cela plus difficile. De plus, j'ai une seule classe frontale pour mon modèle, donc je ne pense pas que le problème d'annulation non-atomique '' sehe'' sera un problème. – Darhuuk

0

Construisez la fonctionnalité d'annulation dans votre modèle lui-même. Laissez-vous modéliser une liste de commandes. Exécutez les commandes dans l'ordre inverse lorsque votre vue transmet un signal d'annulation au modèle.

2

Je recommande vraiment construire l'arbre undo dans votre contrôleur

Construire dans le modèle pourrait vous rencontrez des problèmes:

  • le « modèle » est généralement fragmentée par vue (chaque vue a son propre modèle partiel)
  • cela conduira à undo non atomique (défaisant partie d'une opération en raison de la vue ne sachant pas ce que d'autres choses (modèles) devraient être défaits, etc.)

Le contrôleur est le 'répartiteur d'action', de sorte qu'il aurait à dire

  1. état clone (tous les modèles) instantané
  2. l'action
  3. d'ajouter à l'histoire en se référant à un snapshot
  4. action de course

alors undo serait

  1. l'action pop off Histor pile y (le cas échéant pousser à 'futur' pile)
  2. restauration instantané
  3. vue d'affichage

En outre, rendre le travail undo avec des actions de haut niveau (voir modèle composite ou d'un motif de commande)

+0

Mais maintenant la question est toujours: comment puis-je mettre en œuvre cela proprement? Comment ma classe Commande obtient-elle son instantané? A partir d'une vue implémentant un motif Memento, nécessitant un pointeur vers une vue? D'un getter dans le contrôleur, il fonctionne, brisant ainsi le modèle MVC? – Darhuuk

+0

Le contrôleur _is_ responsable de l'état. Donc, faire en sorte que le contrôleur se souvienne de l'état, ne rompt PAS le modèle MVC. Cependant, il ne devrait pas être nécessaire de l'exposer.Vous pouvez faire Controller.Rollback() - le contrôleur peut éventuellement déléguer l'implémentation réelle au modèle (si elle implémente Memento, par exemple) – sehe

+1

La chose est que je veux obtenir l'état dans mon objet Command. Donc '' Controller.rollback() '' pourrait simplement faire sortir la dernière commande de la pile et appeler '' undo() '' dessus. Je suppose que je pourrais avoir Controller mettre l'état dans la commande avant d'appeler '' execute() '' dessus. Je n'aime pas l'idée de sauver l'état complet cependant (voir modifier dans ma question originale pour plus d'informations). – Darhuuk

Questions connexes