125 votes

Modèle de conception pour le moteur d'annulation

Je suis en train d'écrire un outil de modélisation structurelle pour une application de génie civil. J'ai une énorme classe de modèle représentant l'ensemble du bâtiment, qui comprend des collections de nœuds, d'éléments de ligne, de charges, etc. qui sont également des classes personnalisées.

J'ai déjà codé un moteur d'annulation qui sauvegarde une copie profonde après chaque modification du modèle. Maintenant, je me suis demandé si j'aurais pu coder différemment. Au lieu de sauvegarder les copies profondes, je pourrais peut-être sauvegarder une liste de chaque action de modification avec une modification inverse correspondante. Ainsi, je pourrais appliquer les modificateurs inversés au modèle actuel pour annuler, ou les modificateurs pour refaire.

Je peux imaginer comment vous pourriez exécuter des commandes simples qui modifient les propriétés des objets, etc. Mais qu'en est-il des commandes complexes ? Comme l'insertion de nouveaux objets nœuds dans le modèle et l'ajout d'objets lignes qui conservent les références aux nouveaux nœuds.

Comment s'y prendre pour mettre cela en œuvre ?

0 votes

Si j'ajoute le commentaire "Undo Algorthim", est-ce que cela me permettra de rechercher "Undo Algorithm" et de trouver ceci ? C'est ce que j'ai cherché et j'ai trouvé quelque chose de fermé en tant que duplicata.

0 votes

Nous utilisons le framework QT4 et nous avons besoin d'avoir des actions d'annulation et de rétablissement complexes. Je me demandais si vous aviez réussi à utiliser le Command-Pattern ?

3 votes

@umanga : Ça a marché mais ce n'était pas facile. La partie la plus difficile était de garder la trace des références. Par exemple, lorsqu'un objet Frame est supprimé, ses objets enfants : Nœuds, Charges agissant sur lui et beaucoup d'autres affectations de l'utilisateur devaient être conservés pour être réinsérés lorsqu'ils sont annulés. Mais certains de ces objets enfants étaient partagés avec d'autres objets, et la logique undo/redo devenait assez complexe. Si le modèle n'était pas si grand, je conserverais l'approche du mémento ; elle est beaucoup plus facile à mettre en œuvre.

95voto

Mendelt Points 21583

La plupart des exemples que j'ai vus utilisent une variante de l'option Modèle de commande pour cela. Chaque action de l'utilisateur qui peut être annulée reçoit sa propre instance de commande avec toutes les informations nécessaires pour exécuter l'action et la ramener en arrière. Vous pouvez alors maintenir une liste de toutes les commandes qui ont été exécutées et vous pouvez les annuler une par une.

6 votes

C'est essentiellement la façon dont le moteur d'annulation de Cocoa, NSUndoManager, fonctionne.

0 votes

Qu'est-ce qui est approprié lorsque vous avez des commandes qui devraient être annulables et d'autres qui ne le devraient pas ? En particulier lorsque vous avez un gestionnaire d'annulation/rétablissement qui conserve une pile de commandes ? Peut-être que les commandes non annulables ont leur propre classe, ou peut-être que leur classe send-to-undo-manager ne fait rien ?

1 votes

@EricAuld Je pense que la façon dont vous implémentez cela dépend en grande partie de ce que votre application fait réellement. La sous-classification des commandes semble être une bonne idée de toute façon. Pas seulement pour les commandes annulables et non annulables, mais pour différents types de commandes. Mais comme je l'ai dit, cela dépend beaucoup de l'implémentation.

36voto

Jeff Kotula Points 1737

Je pense que memento et command ne sont pas pratiques lorsqu'il s'agit d'un modèle de la taille et de l'envergure que le PO implique. Ils pourraient fonctionner, mais il y aurait beaucoup de travail pour les maintenir et les étendre.

Pour ce type de problème, je pense que vous devez intégrer un support à votre modèle de données pour prendre en charge les points de contrôle différentiels pour tout objet impliqués dans le modèle. Je l'ai fait une fois et ça a marché très bien. La principale chose à faire est d'éviter l'utilisation directe de pointeurs ou de références dans le modèle.

Chaque référence à un autre objet utilise un identifiant (comme un nombre entier). Chaque fois que l'on a besoin de l'objet, on consulte la définition actuelle de l'objet dans une table. La table contient une liste liée pour chaque objet qui contient toutes les versions précédentes, ainsi que des informations concernant le point de contrôle pour lequel elles étaient actives.

La mise en œuvre de la fonction Annuler/Refaire est simple : Effectuez votre action et établissez un nouveau point de contrôle ; retournez toutes les versions des objets au point de contrôle précédent.

Cela nécessite une certaine discipline dans le code, mais présente de nombreux avantages : vous n'avez pas besoin de copies profondes puisque vous effectuez un stockage différentiel de l'état du modèle ; vous pouvez définir la quantité de mémoire que vous souhaitez utiliser ( très important pour des choses comme les modèles de CAO) par le nombre de redos ou la mémoire utilisée ; très évolutif et à faible maintenance pour les fonctions qui opèrent sur le modèle puisqu'elles n'ont pas besoin de faire quoi que ce soit pour implémenter l'annulation/le rétablissement.

1 votes

Si vous utilisez une base de données (par exemple sqlite) comme format de fichier, cela peut être presque automatique.

4 votes

Si vous ajoutez à cela le suivi des dépendances introduites par les modifications du modèle, vous pouvez potentiellement disposer d'un système d'annulation arborescent (par exemple, si je modifie la largeur d'une poutre, puis que je travaille sur un autre composant, je peux revenir et annuler les modifications de la poutre sans perdre les autres éléments). L'interface utilisateur de ce système pourrait être un peu compliquée, mais elle serait beaucoup plus puissante qu'une annulation linéaire traditionnelle.

0 votes

Pouvez-vous expliquer davantage l'idée des identifiants et des pointeurs ? Un pointeur/adresse mémoire ne fonctionne-t-il pas aussi bien qu'un identifiant ?

17voto

Andy Whitfield Points 1182

Si vous parlez de GoF, le Memento traite spécifiquement de l'annulation.

9 votes

Pas vraiment, cela répond à son approche initiale. Il demande une approche alternative. L'approche initiale consiste à stocker l'état complet pour chaque étape, tandis que la seconde consiste à ne stocker que les "différences".

17voto

Torlack Points 2910

Comme d'autres l'ont dit, le modèle de commande est une méthode très puissante pour mettre en œuvre la fonction Annuler/Refaire. Mais il y a un avantage important que je voudrais mentionner au modèle de commande.

Lors de la mise en œuvre de la fonction Annuler/Refaire à l'aide du modèle de commande, vous pouvez éviter de grandes quantités de code dupliqué en abstrayant (dans une certaine mesure) les opérations effectuées sur les données et en utilisant ces opérations dans le système Annuler/Refaire. Par exemple, dans un éditeur de texte, couper et coller sont des commandes complémentaires (à part la gestion du presse-papiers). En d'autres termes, l'opération d'annulation d'une coupe est le collage et l'opération d'annulation d'un collage est la coupe. Ceci s'applique à des opérations beaucoup plus simples comme la saisie et la suppression de texte.

La clé ici est que vous pouvez utiliser votre système undo/redo comme système de commande principal pour votre éditeur. Au lieu d'écrire un système du type "créer un objet d'annulation, modifier le document", vous pouvez écrire "créer un objet d'annulation, exécuter une opération de rétablissement sur l'objet d'annulation pour modifier le document".

Maintenant, il est vrai que beaucoup de gens se disent "Eh bien, n'est-ce pas là le but du modèle de commande ? Oui, mais j'ai vu trop de systèmes de commande qui ont deux ensembles de commandes, l'un pour les opérations immédiates et l'autre pour les annulations/rétablissements. Je ne dis pas qu'il n'y aura pas de commandes spécifiques aux opérations immédiates et aux annulations/rétablissements, mais la réduction de la duplication rendra le code plus facile à maintenir.

2 votes

Je n'ai jamais pensé à paste comme cut ^-1.

8voto

Adam Davis Points 47683

Vous pouvez vous référer à la Code de Paint.NET pour leur annulation - ils ont un très bon système d'annulation. C'est probablement un peu plus simple que ce dont vous aurez besoin, mais cela peut vous donner des idées et des lignes directrices.

-Adam

5 votes

En fait, le code de Paint.NET n'est plus disponible, mais vous pouvez obtenir l'embranchement. code.google.com/p/paint-mono

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X