J'écris 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 enregistre une copie profonde après chaque modification du modèle. Maintenant, j'ai commencé à me demander si j'aurais pu coder différemment. Au lieu de sauvegarder les copies complètes, je pourrais peut-être enregistrer une liste de chaque action de modificateur avec un modificateur inverse correspondant. Pour que je puisse appliquer les modificateurs inverses au modèle actuel à annuler, ou les modificateurs à refaire.
Je peux imaginer comment vous exécuteriez des commandes simples qui modifient les propriétés des objets, etc. Mais qu'en est-il des commandes complexes? Comme insérer de nouveaux objets de nœud dans le modèle et ajouter des objets de ligne qui conservent des références aux nouveaux nœuds.
Comment procéder pour mettre cela en œuvre?
la source
Réponses:
La plupart des exemples que j'ai vus utilisent une variante du modèle de commande pour cela. Chaque action utilisateur qui peut être annulée obtient sa propre instance de commande avec toutes les informations nécessaires pour exécuter l'action et l'annuler. Vous pouvez ensuite maintenir une liste de toutes les commandes qui ont été exécutées et vous pouvez les annuler une par une.
la source
Je pense que le souvenir et la commande ne sont pas pratiques lorsque vous avez affaire à un modèle de la taille et de la portée que l'OP implique. Ils fonctionneraient, mais ce serait beaucoup de travail à maintenir et à étendre.
Pour ce type de problème, je pense que vous devez intégrer la prise en charge de votre modèle de données pour prendre en charge les points de contrôle différentiels pour chaque objet impliqué dans le modèle. Je l'ai fait une fois et cela a fonctionné très bien. La chose la plus importante à 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 identificateur (comme un entier). Chaque fois que l'objet est nécessaire, vous recherchez la définition actuelle de l'objet dans une table. Le tableau contient une liste chaînée pour chaque objet contenant toutes les versions précédentes, ainsi que des informations sur le point de contrôle pour lequel ils étaient actifs.
La mise en œuvre de undo / redo est simple: faites votre action et établissez un nouveau point de contrôle; restaurer toutes les versions d'objet au point de contrôle précédent.
Cela demande une certaine discipline dans le code, mais présente de nombreux avantages: vous n'avez pas besoin de copies complètes 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 CAO) en fonction du nombre de restaurations ou de la mémoire utilisée; très évolutif et nécessitant peu de maintenance pour les fonctions qui opèrent sur le modèle car elles n'ont rien à faire pour implémenter undo / redo.
la source
Si vous parlez de GoF, le modèle Memento traite spécifiquement de l'annulation.
la source
Comme d'autres l'ont indiqué, le modèle de commande est une méthode très puissante pour implémenter Undo / Redo. Mais il y a un avantage important que je voudrais mentionner au modèle de commande.
Lorsque vous implémentez annuler / rétablir à l'aide du modèle de commande, vous pouvez éviter de grandes quantités de code dupliqué en faisant abstraction (dans une certaine mesure) des opérations effectuées sur les données et en utilisant ces opérations dans le système d'annulation / restauration. Par exemple dans un éditeur de texte couper et coller sont des commandes complémentaires (en dehors de la gestion du presse-papiers). En d'autres termes, l'opération d'annulation pour une coupe est coller et l'opération d'annulation pour une pâte est coupée. Cela 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 d'annulation / rétablissement comme système de commande principal pour votre éditeur. Au lieu d'écrire le système tel que «créer un objet d'annulation, modifier le document», vous pouvez «créer un objet d'annulation, exécuter une opération de rétablissement sur l'objet d'annulation pour modifier le document».
Maintenant, certes, beaucoup de gens se disent "Eh bien, duh, ne fait pas partie du point du modèle de commande?" Oui, mais j'ai vu trop de systèmes de commande qui ont deux ensembles de commandes, un pour les opérations immédiates et un autre pour annuler / rétablir. Je ne dis pas qu'il n'y aura pas de commandes spécifiques aux opérations immédiates et à annuler / rétablir, mais réduire la duplication rendra le code plus maintenable.
la source
paste
àcut
^ -1.Vous voudrez peut-être vous référer au code 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 pourrait vous donner des idées et des directives.
-Adam
la source
Cela pourrait être un cas où la CSLA est applicable. Il a été conçu pour fournir une prise en charge complexe des annulations d'objets dans les applications Windows Forms.
la source
J'ai implémenté avec succès des systèmes d'annulation complexes en utilisant le modèle Memento - très facile, et a l'avantage de fournir naturellement un cadre Redo. Un avantage plus subtil est que les actions agrégées peuvent également être contenues dans une seule annulation.
En un mot, vous avez deux piles d'objets souvenirs. Un pour Annuler, l'autre pour Redo. Chaque opération crée un nouveau souvenir, qui sera idéalement des appels pour changer l'état de votre modèle, document (ou autre). Cela est ajouté à la pile d'annulation. Lorsque vous effectuez une opération d'annulation, en plus d'exécuter l'action Annuler sur l'objet Memento pour modifier à nouveau le modèle, vous faites également sortir l'objet de la pile Annuler et le pousser directement sur la pile Rétablir.
La manière dont la méthode pour changer l'état de votre document est implémentée dépend entièrement de votre implémentation. Si vous pouvez simplement faire un appel API (par exemple ChangeColour (r, g, b)), faites-le précéder d'une requête pour obtenir et enregistrer l'état correspondant. Mais le modèle prendra également en charge la création de copies complètes, d'instantanés de mémoire, de création de fichiers temporaires, etc. - tout dépend de vous car il s'agit simplement d'une implémentation de méthode virtuelle.
Pour effectuer des actions d'agrégation (par exemple, l'utilisateur Shift-Sélectionne une charge d'objets sur lesquels effectuer une opération, telle que supprimer, renommer, modifier l'attribut), votre code crée une nouvelle pile d'annulation en tant que souvenir unique et la transmet à l'opération réelle à ajouter les opérations individuelles à. Ainsi, vos méthodes d'action n'ont pas besoin (a) d'avoir une pile globale à se soucier et (b) peuvent être codées de la même façon qu'elles soient exécutées isolément ou dans le cadre d'une opération d'agrégation.
De nombreux systèmes d'annulation sont uniquement en mémoire, mais vous pouvez conserver la pile d'annulation si vous le souhaitez, je suppose.
la source
Je viens de lire sur le modèle de commande dans mon livre de développement agile - peut-être que cela a du potentiel?
Vous pouvez demander à chaque commande d'implémenter l'interface de commande (qui a une méthode Execute ()). Si vous souhaitez annuler, vous pouvez ajouter une méthode d'annulation.
plus d'infos ici
la source
Je suis avec Mendelt Siebenga sur le fait que vous devriez utiliser le modèle de commande. Le motif que vous avez utilisé était le motif Memento, qui peut devenir et deviendra très inutile avec le temps.
Étant donné que vous travaillez sur une application gourmande en mémoire, vous devriez être en mesure de spécifier la quantité de mémoire que le moteur d'annulation est autorisé à utiliser, le nombre de niveaux d'annulation enregistrés ou le stockage sur lequel ils seront conservés. Si vous ne le faites pas, vous serez bientôt confronté à des erreurs résultant du manque de mémoire de la machine.
Je vous conseillerais de vérifier s'il existe un framework qui a déjà créé un modèle pour les annulations dans le langage de programmation / framework de votre choix. C'est bien d'inventer de nouvelles choses, mais il vaut mieux prendre quelque chose de déjà écrit, débogué et testé dans des scénarios réels. Cela aiderait si vous ajoutiez ce que vous écrivez, afin que les gens puissent recommander des cadres qu'ils connaissent.
la source
Projet Codeplex :
C'est un cadre simple pour ajouter la fonctionnalité Annuler / Rétablir à vos applications, basé sur le modèle de conception classique de Command. Il prend en charge les actions de fusion, les transactions imbriquées, l'exécution retardée (exécution sur la validation de transaction de niveau supérieur) et l'historique d'annulation non linéaire possible (où vous pouvez avoir le choix entre plusieurs actions à refaire).
la source
La plupart des exemples que j'ai lus le font en utilisant la commande ou le modèle memento. Mais vous pouvez également le faire sans motifs de conception avec une simple structure de déque .
la source
Une manière intelligente de gérer l'annulation, qui rendrait votre logiciel également adapté à la collaboration multi-utilisateurs, consiste à mettre en œuvre une transformation opérationnelle de la structure des données.
Ce concept n'est pas très populaire mais bien défini et utile. Si la définition vous semble trop abstraite, ce projet est un exemple réussi de la façon dont une transformation opérationnelle pour les objets JSON est définie et implémentée en Javascript
la source
Pour référence, voici une implémentation simple du modèle de commande pour Annuler / Rétablir en C #: Système simple d'annulation / rétablissement pour C # .
la source
Nous avons réutilisé le chargement de fichier et enregistré le code de sérialisation pour les «objets» pour un formulaire pratique pour enregistrer et restaurer l'état entier d'un objet. Nous poussons ces objets sérialisés sur la pile d'annulation - avec des informations sur l'opération qui a été effectuée et des conseils sur l'annulation de cette opération s'il n'y a pas suffisamment d'informations glanées à partir des données sérialisées. Undo and Redoing consiste souvent simplement à remplacer un objet par un autre (en théorie).
Il y a eu BEAUCOUP de bogues dus à des pointeurs (C ++) vers des objets qui n'ont jamais été corrigés lorsque vous exécutez d'étranges séquences de restauration d'annulation (ces endroits ne sont pas mis à jour pour des «identifiants» plus sûrs). Les bugs dans ce domaine sont souvent ... ummm ... intéressants.
Certaines opérations peuvent être des cas particuliers pour l'utilisation de la vitesse / des ressources - comme le dimensionnement des choses, le déplacement des choses.
La multi-sélection fournit également des complications intéressantes. Heureusement, nous avions déjà un concept de regroupement dans le code. Le commentaire de Kristopher Johnson sur les sous-éléments est assez proche de ce que nous faisons.
la source
J'ai dû faire cela lors de l'écriture d'un solveur pour un jeu de puzzle peg-jump. J'ai fait de chaque mouvement un objet Command qui contenait suffisamment d'informations pour que cela puisse être fait ou annulé. Dans mon cas, c'était aussi simple que de stocker la position de départ et la direction de chaque mouvement. J'ai ensuite stocké tous ces objets dans une pile afin que le programme puisse facilement annuler autant de mouvements que nécessaire tout en effectuant un retour en arrière.
la source
Vous pouvez essayer l'implémentation prête à l'emploi du modèle Undo / Redo dans PostSharp. https://www.postsharp.net/model/undo-redo
Il vous permet d'ajouter des fonctionnalités d'annulation / restauration à votre application sans implémenter le modèle vous-même. Il utilise un modèle enregistrable pour suivre les modifications de votre modèle et fonctionne avec le modèle INotifyPropertyChanged qui est également implémenté dans PostSharp.
Vous disposez de contrôles d'interface utilisateur et vous pouvez décider du nom et de la granularité de chaque opération.
la source
J'ai travaillé une fois sur une application dans laquelle toutes les modifications apportées par une commande au modèle de l'application (c'est-à-dire CDocument ... nous utilisions MFC) étaient persistées à la fin de la commande en mettant à jour les champs dans une base de données interne maintenue dans le modèle. Nous n'avons donc pas eu à écrire de code d'annulation / de rétablissement distinct pour chaque action. La pile d'annulation se souvenait simplement des clés primaires, des noms de champ et des anciennes valeurs à chaque fois qu'un enregistrement était modifié (à la fin de chaque commande).
la source
La première section de Design Patterns (GoF, 1994) présente un cas d'utilisation pour implémenter l'annulation / le rétablissement en tant que modèle de conception.
la source
Vous pouvez rendre votre idée initiale performante.
Utilisez des structures de données persistantes et tenez-vous-en à conserver une liste de références à l'ancien état . (Mais cela ne fonctionne vraiment que si les opérations, toutes les données de votre classe d'état sont immuables, et toutes les opérations sur celle-ci retournent une nouvelle version --- mais la nouvelle version n'a pas besoin d'être une copie complète, remplacez simplement la copie des parties modifiées -on-écriture '.)
la source
J'ai trouvé le modèle de commande très utile ici. Au lieu d'implémenter plusieurs commandes inversées, j'utilise la restauration avec une exécution retardée sur une deuxième instance de mon API.
Cette approche semble raisonnable si vous voulez un faible effort d'implémentation et une facilité de maintenance (et pouvez vous permettre la mémoire supplémentaire pour la 2ème instance).
Voir ici pour un exemple: https://github.com/thilo20/Undo/
la source
Je ne sais pas si cela va vous être utile, mais quand j'ai dû faire quelque chose de similaire sur l'un de mes projets, j'ai fini par télécharger UndoEngine à partir de http://www.undomadeeasy.com - un moteur merveilleux et je ne me souciais vraiment pas trop de ce qu'il y avait sous le capot - cela fonctionnait.
la source
À mon avis, l'UNDO / REDO pourrait être mis en œuvre de deux manières au sens large. 1. Niveau de commande (appelé niveau de commande Undo / Redo) 2. Niveau de document (appelé global Undo / Redo)
Niveau de commande: comme le soulignent de nombreuses réponses, cela est efficacement réalisé en utilisant le modèle Memento. Si la commande prend également en charge la journalisation de l'action, une restauration est facilement prise en charge.
Limitation: une fois que la portée de la commande est sortie, l'annulation / le rétablissement est impossible, ce qui conduit à l'annulation / rétablissement au niveau du document (global)
Je suppose que votre cas s'intégrerait dans l'annulation / le rétablissement global car il convient à un modèle qui implique beaucoup d'espace mémoire. En outre, cela convient également pour annuler / rétablir sélectivement. Il existe deux types primitifs
Dans "Toute la mémoire Undo / Redo", toute la mémoire est traitée comme une donnée connectée (comme un arbre, une liste ou un graphique) et la mémoire est gérée par l'application plutôt que par l'OS. Ainsi, les opérateurs new et delete si en C ++ sont surchargés pour contenir des structures plus spécifiques pour implémenter efficacement des opérations telles que a. Si un nœud est modifié, b. conserver et effacer les données, etc., son fonctionnement consiste essentiellement à copier toute la mémoire (en supposant que l'allocation de mémoire est déjà optimisée et gérée par l'application à l'aide d'algorithmes avancés) et à la stocker dans une pile. Si la copie de la mémoire est demandée, l'arborescence est copiée en fonction de la nécessité d'avoir une copie superficielle ou profonde. Une copie complète est effectuée uniquement pour cette variable qui est modifiée. Étant donné que chaque variable est allouée à l'aide d'une allocation personnalisée, l'application a le dernier mot pour la supprimer le cas échéant. Les choses deviennent très intéressantes si nous devons partitionner le Undo / Redo quand il se trouve que nous devons annuler / rétablir de manière sélective par programme un ensemble d'opérations. Dans ce cas, seules ces nouvelles variables, ou les variables supprimées ou les variables modifiées reçoivent un drapeau afin que Annuler / Rétablir uniquement annule / répète ces mémoires. Lorsque tel est le cas, une nouvelle idée de «modèle de visiteur» est utilisée. Il s’appelle «Annuler / rétablir au niveau de l’objet» ou les variables supprimées ou les variables modifiées reçoivent un drapeau pour qu'Undo / Redo n'annule / ne rétablisse que ces mémoires. Les choses deviennent encore plus intéressantes si nous devons faire un Undo / Redo partiel à l'intérieur d'un objet. Lorsque tel est le cas, une nouvelle idée de «modèle de visiteur» est utilisée. Il s’appelle «Annuler / rétablir au niveau de l’objet» ou les variables supprimées ou les variables modifiées reçoivent un drapeau pour qu'Undo / Redo n'annule / ne rétablisse que ces mémoires. Les choses deviennent encore plus intéressantes si nous devons faire un Undo / Redo partiel à l'intérieur d'un objet. Lorsque tel est le cas, une nouvelle idée de «modèle de visiteur» est utilisée. Il s’appelle «Annuler / rétablir au niveau de l’objet»
1 et 2 peuvent avoir des méthodes telles que 1. BeforeUndo () 2. AfterUndo () 3. BeforeRedo () 4. AfterRedo (). Ces méthodes doivent être publiées dans la commande de base Undo / Redo (pas dans la commande contextuelle) afin que tous les objets implémentent également ces méthodes pour obtenir une action spécifique.
Une bonne stratégie consiste à créer un hybride de 1 et 2. La beauté est que ces méthodes (1 et 2) utilisent elles-mêmes des modèles de commande
la source