Domanda

Sto lavorando a un piccolo progetto di editor UML, in Java, che ho iniziato un paio di mesi fa. Dopo alcune settimane, ho ricevuto una copia funzionante per un editor di diagrammi di classe UML.

Ma ora, lo sto riprogettando completamente per supportare altri tipi di diagrammi, come una sequenza, stato, classe, ecc. Questo è fatto implementando un framework di costruzione di grafici (sono fortemente ispirato dal lavoro di Cay Horstmann sul soggetto con l'editor UML di Violet).

La riprogettazione procedeva senza intoppi fino a quando uno dei miei amici non mi disse che avevo dimenticato di aggiungere una funzionalità Do / Undo al progetto, che, secondo me, è vitale.

Ricordando i corsi di design orientati agli oggetti, ho subito pensato al modello Memento e Command.

Ecco l'affare. Ho una classe astratta, AbstractDiagram, che contiene due ArrayLists: una per l'archiviazione dei nodi (chiamati Elements nel mio progetto) e l'altra per l'archiviazione dei bordi (chiamati Links nei miei progetti). Il diagramma probabilmente manterrà una pila di comandi che possono essere annullati / ripristinati. Piuttosto standard.

Come posso eseguire questi comandi in modo efficiente? Diciamo, ad esempio, che voglio spostare un nodo (il nodo sarà un tipo di interfaccia chiamato INode e da esso deriveranno nodi concreti (ClassNode, InterfaceNode, NoteNode, ecc.)).

Le informazioni sulla posizione vengono mantenute come un attributo nel nodo, quindi modificando tale attributo nel nodo stesso, lo stato viene modificato. Quando il display verrà aggiornato, il nodo si sarà spostato. Questa è la parte Memento del modello (penso), con la differenza che l'oggetto è lo stato stesso.

Inoltre, se mantengo un clone del nodo originale (prima che si spostasse), posso tornare alla sua vecchia versione. La stessa tecnica si applica alle informazioni contenute nel nodo (il nome della classe o dell'interfaccia, il testo per un nodo nota, il nome degli attributi e così via).

Il problema è, come posso sostituire, nel diagramma, il nodo con il suo clone dopo l'operazione di annullamento / ripetizione? Se clono l'oggetto originale a cui fa riferimento il diagramma (trovandosi nell'elenco dei nodi), il clone non fa riferimento nel diagramma e l'unica cosa a cui punta è il comando stesso! Shoud Includo meccanismi nel diagramma per trovare un nodo secondo un ID (per esempio) in modo da poter sostituire, nel diagramma, il nodo con il suo clone (e viceversa)? Dipende dai modelli Memento e Command farlo? E i link? Dovrebbero anche essere mobili ma non voglio creare un comando solo per i collegamenti (e uno solo per i nodi) e dovrei essere in grado di modificare l'elenco corretto (nodi o collegamenti) in base al tipo di oggetto del comando si riferisce a.

Come procederesti? In breve, ho problemi a rappresentare lo stato di un oggetto in un modello di comando / ricordo in modo che possa essere recuperato in modo efficiente e l'oggetto originale ripristinato nell'elenco dei diagrammi e in base al tipo di oggetto (nodo o collegamento).

Grazie mille!

Guillaume.

P.S .: se non sono chiaro, dimmelo e chiarirò il mio messaggio (come sempre!).

Modifica

Ecco la mia vera soluzione, che ho iniziato a implementare prima di pubblicare questa domanda.

Innanzitutto, ho una classe AbstractCommand definita come segue:

public abstract class AbstractCommand {
    public boolean blnComplete;

    public void setComplete(boolean complete) {
        this.blnComplete = complete;
    }

    public boolean isComplete() {
        return this.blnComplete;
    }

    public abstract void execute();
    public abstract void unexecute();
}

Quindi, ogni tipo di comando viene implementato usando una derivazione concreta di AbstractCommand.

Quindi ho un comando per spostare un oggetto:

public class MoveCommand extends AbstractCommand {
    Moveable movingObject;
    Point2D startPos;
    Point2D endPos;

    public MoveCommand(Point2D start) {
        this.startPos = start;
    }

    public void execute() {
        if(this.movingObject != null && this.endPos != null)
            this.movingObject.moveTo(this.endPos);
    }

    public void unexecute() {
        if(this.movingObject != null && this.startPos != null)
            this.movingObject.moveTo(this.startPos);
    }

    public void setStart(Point2D start) {
        this.startPos = start;
    }

    public void setEnd(Point2D end) {
        this.endPos = end;
    }
}

Ho anche un MoveRemoveCommand (per ... spostare o rimuovere un oggetto / nodo). Se uso l'ID del metodo instanceof, non devo passare il diagramma al nodo o al collegamento effettivo in modo che possa rimuoverlo dal diagramma (che è una cattiva idea penso).

Diagramma astratto del diagramma;     Addj obj;     Tipo AddRemoveType;

@SuppressWarnings("unused")
private AddRemoveCommand() {}

public AddRemoveCommand(AbstractDiagram diagram, Addable obj, AddRemoveType type) {
    this.diagram = diagram;
    this.obj = obj;
    this.type = type;
}

public void execute() {
    if(obj != null && diagram != null) {
        switch(type) {
            case ADD:
                this.obj.addToDiagram(diagram);
                break;
            case REMOVE:
                this.obj.removeFromDiagram(diagram);
                break;
        }
    }
}

public void unexecute() {
    if(obj != null && diagram != null) {
        switch(type) {
            case ADD:
                this.obj.removeFromDiagram(diagram);
                break;
            case REMOVE:
                this.obj.addToDiagram(diagram);
                break;
        }
    }
}

Infine, ho un comando di modifica che viene utilizzato per modificare le informazioni di un nodo o collegamento (nome della classe, ecc.). Questo potrebbe essere unito in futuro con MoveCommand. Questa classe è vuota per ora. Probabilmente farò la cosa ID con un meccanismo per determinare se l'oggetto modificato è un nodo o un bordo (tramite istanza di un denotione speciale nell'ID).

Questa è una buona soluzione?

È stato utile?

Soluzione

Penso che devi solo scomporre il tuo problema in più piccoli.

Primo problema: D: Come rappresentare i passaggi nella tua app con il modello ricordo / comando? Prima di tutto, non ho idea di come funzioni la tua app, ma spero che vedrai dove sto andando. Supponiamo di voler posizionare un ClassNode sul diagramma con le seguenti proprietà

{ width:100, height:50, position:(10,25), content:"Am I certain?", edge-connections:null}

Sarebbe racchiuso in un oggetto comando. Supponiamo che vada a un DiagramController. Quindi la responsabilità del controllore del diagramma può essere quella di registrare quel comando (spingere su uno stack sarebbe la mia scommessa) e passare il comando a un DiagramBuilder per esempio. DiagramBuilder sarebbe effettivamente responsabile dell'aggiornamento del display.

DiagramController
{
  public DiagramController(diagramBuilder:DiagramBuilder)
  {
    this._diagramBuilder = diagramBuilder;
    this._commandStack = new Stack();
  }

  public void Add(node:ConditionalNode)
  {
    this._commandStack.push(node);
    this._diagramBuilder.Draw(node);
  }

  public void Undo()
  {
    var node = this._commandStack.pop();
    this._diagramBuilderUndraw(node);
  }
}

Qualcosa del genere dovrebbe farlo e ovviamente ci saranno molti dettagli da risolvere. A proposito, più proprietà hanno i tuoi nodi più deve essere dettagliato Undraw.

L'uso di un ID per collegare il comando nella pila all'elemento disegnato potrebbe essere una buona idea. Potrebbe apparire così:

DiagramController
{
  public DiagramController(diagramBuilder:DiagramBuilder)
  {
    this._diagramBuilder = diagramBuilder;
    this._commandStack = new Stack();
  }

  public void Add(node:ConditionalNode)
  {
    string graphicalRefId = this._diagramBuilder.Draw(node);
    var nodePair = new KeyValuePair<string, ConditionalNode> (graphicalRefId, node);
    this._commandStack.push(nodePair);
  }

  public void Undo()
  {
    var nodePair = this._commandStack.pop();
    this._diagramBuilderUndraw(nodePair.Key);
  }
} 

A questo punto non devi assolutamente avere l'oggetto poiché hai l'ID ma sarà utile se decidi di implementare anche la funzionalità di ripetizione. Un buon modo per generare l'id per i tuoi nodi sarebbe implementare un metodo hashcode per loro, tranne per il fatto che non ti garantiresti di non duplicare i tuoi nodi in modo tale da rendere identico il codice hash.

La parte successiva del problema è all'interno di DiagramBuilder perché stai cercando di capire come diamine gestire questi comandi. Per quello che posso solo dire è assicurarmi davvero di poter creare un'azione inversa per ogni tipo di componente che è possibile aggiungere. Per gestire il delinking puoi guardare la proprietà edge-connection (penso che i collegamenti nel tuo codice) e notificare a ciascuna delle edge-connessioni che devono disconnettersi dal nodo specifico. Suppongo che al momento della disconnessione potrebbero ridisegnarsi in modo appropriato.

Per riassumere un po ', consiglierei di non tenere un riferimento ai tuoi nodi nello stack ma invece solo una specie di token che rappresenta lo stato di un dato nodo in quel punto. Ciò ti consentirà di rappresentare lo stesso nodo nello stack di annullamento in più punti senza che si riferisca allo stesso oggetto.

Pubblica se hai Q's. Questo è un problema complesso.

Altri suggerimenti

Secondo la mia modesta opinione, lo stai pensando in un modo più complicato di quanto non sia in realtà. Per tornare allo stato precedente, non è necessario clonare l'intero nodo. Piuttosto ogni * * classe di comando avrà -

  1. riferimento al nodo su cui agisce,
  2. oggetto memento (con variabili di stato sufficienti per ripristinare il nodo)
  3. metodo execute ()
  4. metodo undo ().

Poiché le classi di comandi hanno riferimento al nodo, non abbiamo bisogno del meccanismo ID per fare riferimento agli oggetti nel diagramma.

Nell'esempio della tua domanda, vogliamo spostare un nodo in una nuova posizione. Per questo, abbiamo una classe NodePositionChangeCommand.

public class NodePositionChangeCommand {
    // This command will act upon this node
    private Node node;

    // Old state is stored here
    private NodePositionMemento previousNodePosition;

    NodePositionChangeCommand(Node node) {
        this.node = node;
    }

    public void execute(NodePositionMemento newPosition) {
        // Save current state in memento object previousNodePosition

        // Act upon this.node
    }

    public void undo() {
        // Update this.node object with values from this.previousNodePosition
    }
}
  
    

E i link? Dovrebbero anche essere mobili ma non voglio creare un comando solo per i collegamenti (e uno solo per i nodi).

  

Ho letto nel libro GoF (nella discussione del modello di ricordo) che lo spostamento del collegamento con il cambiamento nella posizione dei nodi è gestito da un qualche tipo di risolutore di vincoli.

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