Domanda

Sto scrivendo uno strumento di modellazione strutturale per un'applicazione di ingegneria civile.Ho un'enorme classe di modelli che rappresenta l'intero edificio, che include raccolte di nodi, elementi di linea, carichi, ecc.che sono anche classi personalizzate.

Ho già codificato un motore di annullamento che salva una copia profonda dopo ogni modifica al modello.Ora ho iniziato a pensare se avrei potuto codificare diversamente.Invece di salvare le copie profonde, potrei forse salvare un elenco di ciascuna azione di modificatore con un modificatore inverso corrispondente.In modo da poter applicare i modificatori inversi al modello corrente da annullare o i modificatori da ripetere.

Posso immaginare come eseguiresti semplici comandi che modificano le proprietà dell'oggetto, ecc.Ma che ne dici di comandi complessi?Come inserire nuovi oggetti nodo nel modello e aggiungere alcuni oggetti linea che mantengono i riferimenti ai nuovi nodi.

Come si potrebbe implementarlo?

È stato utile?

Soluzione

La maggior parte degli esempi che ho visto utilizzano una variante di Modello di comando per questo.Ogni azione dell'utente annullabile riceve la propria istanza di comando con tutte le informazioni per eseguire l'azione e ripristinarla.È quindi possibile mantenere un elenco di tutti i comandi che sono stati eseguiti e ripristinarli uno per uno.

Altri suggerimenti

Penso che sia il ricordo che il comando non siano pratici quando si ha a che fare con un modello delle dimensioni e della portata implicate dall'OP.Funzionerebbero, ma sarebbe molto lavoro mantenerli ed estenderli.

Per questo tipo di problema, penso che sia necessario integrare il supporto per il modello dati per supportare i checkpoint differenziali ogni oggetto coinvolti nel modello.L'ho fatto una volta e ha funzionato molto bene.La cosa più importante da fare è evitare l'uso diretto di puntatori o riferimenti nel modello.

Ogni riferimento a un altro oggetto utilizza un identificatore (come un numero intero).Ogni volta che è necessario l'oggetto, si cerca la definizione corrente dell'oggetto da una tabella.La tabella contiene un elenco collegato per ciascun oggetto che contiene tutte le versioni precedenti, insieme alle informazioni relative a quale checkpoint erano attive.

Implementare l'annullamento/ripristino è semplice:Esegui la tua azione e stabilisci un nuovo checkpoint;eseguire il rollback di tutte le versioni dell'oggetto al checkpoint precedente.

Richiede una certa disciplina nel codice, ma presenta molti vantaggi:non sono necessarie copie profonde poiché stai eseguendo l'archiviazione differenziale dello stato del modello;puoi definire la quantità di memoria che desideri utilizzare (molto importante per cose come i modelli CAD) in base al numero di ripetizioni o alla memoria utilizzata;molto scalabile e a bassa manutenzione per le funzioni che operano sul modello poiché non è necessario fare nulla per implementare l'annullamento/ripristino.

Se stai parlando di GoF, il Ricordo il modello affronta specificamente l'annullamento.

Come altri hanno affermato, il modello di comando è un metodo molto potente per implementare Annulla/Ripeti.Ma c'è un vantaggio importante che vorrei menzionare nel modello di comando.

Quando si implementa l'operazione di annullamento/ripristino utilizzando il modello di comando, è possibile evitare grandi quantità di codice duplicato astraendo (in una certa misura) le operazioni eseguite sui dati e utilizzando tali operazioni nel sistema di annullamento/ripetizione.Ad esempio in un editor di testo taglia e incolla sono comandi complementari (a parte la gestione degli appunti).In altre parole, l'operazione di annullamento per un taglio è incolla e l'operazione di annullamento per un incolla è taglia.Questo vale per operazioni molto più semplici come digitare ed eliminare testo.

La chiave qui è che puoi usare il tuo sistema di annullamento/ripristino come sistema di comando principale per il tuo editor.Invece di scrivere il sistema come "crea oggetto annulla, modifica il documento" puoi "crea oggetto annulla, esegui l'operazione di ripetizione sull'oggetto annulla per modificare il documento".

Ora, certamente, molte persone pensano a se stesse "bene duh, non fa parte del punto del modello di comando?" Sì, ma ho visto troppi sistemi di comando che hanno due set di comandi, uno per operazioni immediate e un altro set per annullamento/redo.Non sto dicendo che non ci saranno comandi specifici per operazioni immediate e annulla/ripristina, ma ridurre la duplicazione renderà il codice più gestibile.

Potresti voler fare riferimento a Codice Paint.NET per l'annullamento: hanno un sistema di annullamento davvero carino.Probabilmente è un po' più semplice di quello di cui avrai bisogno, ma potrebbe darti alcune idee e linee guida.

-Adamo

Questo potrebbe essere un caso in cui CSLA è applicabile.È stato progettato per fornire supporto di annullamento complesso agli oggetti nelle applicazioni Windows Forms.

Ho implementato con successo sistemi di annullamento complessi utilizzando il pattern Memento: molto semplice e ha il vantaggio di fornire naturalmente anche un framework Redo.Un vantaggio più sottile è che anche le azioni aggregate possono essere contenute in un singolo Annulla.

In poche parole, hai due pile di oggetti ricordo.Uno per Annulla, l'altro per Ripeti.Ogni operazione crea un nuovo ricordo, che idealmente sarà costituito da alcune chiamate per modificare lo stato del tuo modello, documento (o altro).Questo viene aggiunto allo stack di annullamento.Quando esegui un'operazione di annullamento, oltre a eseguire l'azione Annulla sull'oggetto Memento per ripristinare nuovamente il modello, estrai anche l'oggetto dallo stack Annulla e lo spingi direttamente nello stack Redo.

Il modo in cui viene implementato il metodo per modificare lo stato del documento dipende completamente dalla tua implementazione.Se puoi semplicemente effettuare una chiamata API (ad es.ChangeColour(r,g,b)), quindi precederlo con una query per ottenere e salvare lo stato corrispondente.Ma il modello supporterà anche la creazione di copie profonde, istantanee della memoria, creazione di file temporanei ecc. Dipende tutto da te in quanto è semplicemente un'implementazione del metodo virtuale.

Per eseguire azioni aggregate (ad es.utente Shift-Seleziona un carico di oggetti su cui eseguire un'operazione, come eliminare, rinominare, modificare attributo), il codice crea un nuovo stack di annullamento come singolo ricordo e lo passa all'operazione effettiva a cui aggiungere le singole operazioni.Pertanto i tuoi metodi di azione non devono (a) avere uno stack globale di cui preoccuparsi e (b) possono essere codificati allo stesso modo sia che vengano eseguiti isolatamente o come parte di un'operazione aggregata.

Molti sistemi di annullamento sono solo in memoria, ma potresti persistere lo stack di annullamento se lo desideri, immagino.

Ho appena letto del modello di comando nel mio libro sullo sviluppo agile: forse ha del potenziale?

Puoi fare in modo che ogni comando implementi l'interfaccia di comando (che ha un metodo Execute()).Se vuoi annullare, puoi aggiungere un metodo Annulla.

Ulteriori informazioni Qui

sono con Mendelt Siebenga sul fatto che dovresti usare il modello di comando.Il modello che hai utilizzato era il Memento Pattern, che può diventare e diventerà molto dispendioso nel tempo.

Dato che stai lavorando su un'applicazione ad uso intensivo di memoria, dovresti essere in grado di specificare quanta memoria può occupare il motore di annullamento, quanti livelli di annullamento vengono salvati o qualche spazio di archiviazione in cui verranno mantenuti.Se non lo fai, presto dovrai affrontare errori derivanti dalla mancanza di memoria della macchina.

Ti consiglierei di verificare se esiste un framework che ha già creato un modello per gli annullamenti nel linguaggio/framework di programmazione di tua scelta.È bello inventare cose nuove, ma è meglio prendere qualcosa di già scritto, debuggato e testato in scenari reali.Sarebbe utile se aggiungessi ciò in cui stai scrivendo, in modo che le persone possano consigliare i framework che conoscono.

Progetto Codeplex:

È un framework semplice per aggiungere funzionalità Annulla/Ripeti alle tue applicazioni, basato sul classico modello di progettazione Command.Supporta azioni di unione, transazioni nidificate, esecuzione ritardata (esecuzione su commit della transazione di livello superiore) e possibile cronologia di annullamento non lineare (dove è possibile scegliere tra più azioni da ripetere).

La maggior parte degli esempi che ho letto lo fanno utilizzando il comando o il pattern memento.Ma puoi farlo anche senza modelli di progettazione con un semplice struttura deque.

Un modo intelligente per gestire l'annullamento, che renderebbe il tuo software adatto anche alla collaborazione multiutente, è implementare un file trasformazione operativa della struttura dei dati.

Questo concetto non è molto popolare ma ben definito e utile.Se la definizione ti sembra troppo astratta, questo progetto è un esempio riuscito di come una trasformazione operativa per oggetti JSON viene definita e implementata in Javascript

Per riferimento, ecco una semplice implementazione del modello Command per Annulla/Ripeti in C#: Semplice sistema di annullamento/ripetizione per C#.

Abbiamo riutilizzato il caricamento del file e salvato il codice di serializzazione per gli "oggetti" per un modulo conveniente per salvare e ripristinare l'intero stato di un oggetto.Inseriamo questi oggetti serializzati nello stack di annullamento, insieme ad alcune informazioni su quale operazione è stata eseguita e suggerimenti su come annullare tale operazione se non ci sono abbastanza informazioni raccolte dai dati serializzati.Annullare e ripetere spesso significa semplicemente sostituire un oggetto con un altro (in teoria).

Ci sono stati molti bug dovuti a puntatori (C++) a oggetti che non sono mai stati corretti mentre si eseguivano alcune strane sequenze di ripristino dell'annullamento (quei luoghi non aggiornati per "identificatori" consapevoli dell'annullamento più sicuri).I bug in quest'area spesso...ummm...interessante.

Alcune operazioni possono essere casi speciali per velocità/utilizzo delle risorse, come dimensionare oggetti, spostare oggetti.

La selezione multipla fornisce anche alcune complicazioni interessanti.Fortunatamente avevamo già un concetto di raggruppamento nel codice.Il commento di Kristopher Johnson sulle voci secondarie è abbastanza vicino a ciò che facciamo.

Ho dovuto farlo mentre scrivevo un risolutore per un gioco di puzzle peg-jump.Ho reso ogni mossa un oggetto Comando che conteneva informazioni sufficienti per poter essere eseguita o annullata.Nel mio caso è stato semplice come memorizzare la posizione iniziale e la direzione di ogni movimento.Ho quindi memorizzato tutti questi oggetti in una pila in modo che il programma potesse facilmente annullare tutte le mosse necessarie durante il backtracking.

Puoi provare l'implementazione già pronta del pattern Annulla/Ripeti in PostSharp. https://www.postsharp.net/model/undo-redo

Ti consente di aggiungere funzionalità di annullamento/ripetizione alla tua applicazione senza implementare tu stesso il pattern.Utilizza il pattern Recordable per tenere traccia delle modifiche nel modello e funziona con il pattern INotifyPropertyChanged, anch'esso implementato in PostSharp.

Ti vengono forniti i controlli dell'interfaccia utente e puoi decidere quale sarà il nome e la granularità di ciascuna operazione.

Una volta ho lavorato su un'applicazione in cui tutte le modifiche apportate da un comando al modello dell'applicazione (ad es.Documento CD...stavamo utilizzando MFC) venivano mantenuti alla fine del comando aggiornando i campi in un database interno mantenuto all'interno del modello.Quindi non abbiamo dovuto scrivere un codice di annullamento/ripristino separato per ciascuna azione.Lo stack di annullamento ricordava semplicemente le chiavi primarie, i nomi dei campi e i vecchi valori ogni volta che un record veniva modificato (alla fine di ogni comando).

La prima sezione di Design Patterns (GoF, 1994) presenta un caso d'uso per implementare l'annullamento/ripristino come modello di progettazione.

Puoi rendere performante la tua idea iniziale.

Utilizzo strutture dati persistenti, e continua a mantenere a elenco dei riferimenti al vecchio stato in giro.(Ma funziona davvero solo se le operazioni su tutti i dati nella classe di stato sono immutabili e tutte le operazioni su di essa restituiscono una nuova versione --- ma la nuova versione non deve essere una copia profonda, basta sostituire la copia delle parti modificate -on-write'.)

Ho trovato il modello Command molto utile qui.Invece di implementare diversi comandi inversi, sto utilizzando il rollback con esecuzione ritardata su una seconda istanza della mia API.

Questo approccio sembra ragionevole se si desidera un basso sforzo di implementazione e una facile manutenibilità (e si può permettersi la memoria aggiuntiva per la seconda istanza).

Vedi qui per un esempio:https://github.com/thilo20/Annulla/

Non so se ti sarà utile, ma quando ho dovuto fare qualcosa di simile su uno dei miei progetti, ho finito per scaricare UndoEngine da http://www.undomadeeasy.com - un motore meraviglioso e non mi importava molto di cosa c'era sotto il cofano - funzionava e basta.

A mio parere, UNDO/REDO potrebbe essere implementato in 2 modi in generale.1.Livello di comando (chiamato livello di comando Annulla/RIDO) 2.Livello documento (chiamato Annulla/Ripeti globale)

Livello di comando:Come sottolineano molte risposte, ciò si ottiene in modo efficiente utilizzando il modello Memento.Se il comando supporta anche la registrazione dell'azione nel journal, è facilmente supportata una ripetizione.

Limitazione:Una volta che l'ambito del comando è fuori, l'annullamento/ripetizione è impossibile, il che porta all'annullamento/ripetizione a livello di documento (globale)

Immagino che il tuo caso si adatterebbe all'annullamento/ripetizione globale poiché è adatto per un modello che richiede molto spazio di memoria.Inoltre, questo è adatto anche per annullare/ripetere selettivamente.Esistono due tipi primitivi

  1. Annulla/ripristina tutta la memoria
  2. Livello oggetto Annulla Ripeti

In "Annulla/Ripeti tutta la memoria", l'intera memoria viene trattata come un dato connesso (come un albero, un elenco o un grafico) e la memoria è gestita dall'applicazione anziché dal sistema operativo.Quindi gli operatori new ed delete se in C++ sono sovraccarichi per contenere strutture più specifiche per implementare in modo efficace operazioni come a.Se un nodo viene modificato, b.Tenere e cancellare i dati ecc., Il modo in cui funziona è fondamentalmente per copiare l'intera memoria (supponendo che l'allocazione della memoria sia già ottimizzata e gestita dall'applicazione utilizzando algoritmi avanzati) e memorizzarla in uno stack.Se viene richiesta la copia della memoria viene copiata la struttura ad albero in base alla necessità di avere una copia superficiale o profonda.Viene eseguita una copia approfondita solo per la variabile modificata.Poiché ogni variabile viene allocata utilizzando l'allocazione personalizzata, l'applicazione ha l'ultima parola su quando eliminarla, se necessario.Le cose diventano molto interessanti se dobbiamo partizionare l'operazione Annulla/Ripeti quando accade che dobbiamo annullare/ripetire in modo programmatico e selettivo una serie di operazioni.In questo caso, solo quelle nuove variabili, o variabili eliminate o variabili modificate ricevono una bandiera in modo da annullare/rifare solo annullate/ridotta quelle cose di memoria diventano ancora più interessanti se abbiamo bisogno di fare un annullamento parziale/rifare all'interno di un oggetto.In tal caso, viene utilizzata una nuova idea di "modello visitatore".Si chiama "Annulla/ripeti a livello oggetto"

  1. Livello oggetto Annulla/Ripeti:Quando viene richiamata la notifica di annullamento/ripetizione, ogni oggetto implementa un'operazione di streaming in cui lo streamer ottiene dall'oggetto i vecchi/nuovi dati programmati.I dati che non devono essere disturbati vengono lasciati indisturbati.Ogni oggetto riceve uno streamer come argomento e all'interno della chiamata UNDo/Redo esegue lo streaming/unstreaming dei dati dell'oggetto.

Sia 1 che 2 potrebbero avere metodi come 1.Primavo () 2.Afterundo () 3.BeForeDo () 4.DopoRifai().Questi metodi devono essere pubblicati nel comando Annulla/Ripeti di base (non nel comando contestuale) in modo che anche tutti gli oggetti implementino questi metodi per ottenere un'azione specifica.

Una buona strategia è creare un ibrido tra 1 e 2.Il bello è che questi metodi (1 e 2) utilizzano schemi di comando

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top