Question

Je travaille sur un petit projet d’éditeur UML, en Java, que j’ai démarré il ya quelques mois. Après quelques semaines, j'ai reçu une copie de travail pour un éditeur de diagramme de classes UML.

Mais maintenant, je le redessine complètement pour prendre en charge d'autres types de diagrammes, tels qu'une séquence, un état, une classe, etc. Ceci est réalisé en implémentant un cadre de construction de graphe (je suis très inspiré par le travail de Cay Horstmann sur la avec l’éditeur Violet UML).

La refonte se déroulait sans heurt jusqu’à ce qu’un de mes amis me dise que j’ai oublié d’ajouter une fonctionnalité Do / Undo au projet, ce qui, à mon avis, est vital.

En pensant aux cours de conception orientée objet, j'ai tout de suite pensé au modèle Memento et Command.

Voici le deal. J'ai une classe abstraite, AbstractDiagram, qui contient deux ArrayLists: une pour stocker les nœuds (appelés éléments dans mon projet) et l'autre pour stocker les bords (appelés liens dans mes projets). Le diagramme conservera probablement une pile de commandes pouvant être annulées / rétablies. Joli standard.

Comment puis-je exécuter ces commandes de manière efficace? Disons, par exemple, que je souhaite déplacer un nœud (le nœud sera un type d'interface nommé INode, et des nœuds concrets en seront dérivés (ClassNode, InterfaceNode, NoteNode, etc.).

Les informations de position sont conservées sous forme d'attribut dans le nœud. Ainsi, en modifiant cet attribut dans le nœud lui-même, l'état est modifié. Lorsque l'affichage sera actualisé, le nœud sera déplacé. C’est la partie mémento du motif (je pense), à ??la différence que l’objet est l’état lui-même.

De plus, si je conserve un clone du nœud d'origine (avant son déplacement), je peux revenir à sa version précédente. La même technique s'applique aux informations contenues dans le nœud (le nom de classe ou d'interface, le texte d'un nœud de note, le nom de l'attribut, etc.).

Le problème est de savoir comment remplacer, dans le diagramme, le nœud avec son opération de clonage après annulation / restauration. Si je clone l'objet d'origine référencé par le diagramme (dans la liste des nœuds), le clone n'est pas référencé dans le diagramme et la seule chose qui pointe vers est la commande elle-même! Devrais-je inclure des mécanismes dans le diagramme pour rechercher un nœud en fonction d'un ID (par exemple) afin de pouvoir remplacer, dans le diagramme, le nœud par son clone (et inversement)? Est-ce que cela revient aux modèles de mémento et de commande? Qu'en est-il des liens? Ils devraient être déplaçables aussi, mais je ne veux pas créer une commande uniquement pour les liens (et une pour les nœuds), et je devrais pouvoir modifier la liste de droite (nœuds ou liens) en fonction du type d'objet de la commande. fait référence à.

Comment procéderiez-vous? En bref, j'ai du mal à représenter l'état d'un objet dans un modèle commande / mémento afin qu'il puisse être efficacement récupéré et que l'objet d'origine soit restauré dans la liste des diagrammes, et en fonction du type d'objet (noeud ou lien).

Merci beaucoup!

Guillaume.

P.S .: si je ne suis pas clair, dites-le-moi et je clarifierai mon message (comme toujours!).

Modifier

Voici ma solution actuelle, que j'ai commencé à implémenter avant de poster cette question.

Tout d'abord, j'ai une classe AbstractCommand définie comme suit:

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();
}

Ensuite, chaque type de commande est implémenté à l'aide d'une dérivation concrète de AbstractCommand.

J'ai donc une commande pour déplacer un objet:

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;
    }
}

J'ai aussi une MoveRemoveCommand (pour ... déplacer ou supprimer un objet / noeud). Si j'utilise l'ID de la méthode instanceof, je n'ai pas à passer le diagramme au noeud ou au lien afin qu'il puisse se retirer du diagramme (ce qui est une mauvaise idée, je pense).

AbstractDiagram diagramme;     Addable obj;     Type 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;
        }
    }
}

Enfin, j’ai une ModificationCommand qui permet de modifier les informations d’un nœud ou d’un lien (nom de classe, etc.). Cela peut être fusionné à l'avenir avec la MoveCommand. Cette classe est vide pour l'instant. Je ferai probablement la même chose avec un mécanisme permettant de déterminer si l'objet modifié est un nœud ou une arête (via instanceof ou une dénonciation spéciale dans l'ID).

Est-ce une bonne solution?

Était-ce utile?

La solution

Je pense que vous avez juste besoin de décomposer votre problème en problèmes plus petits.

Premier problème: Q: Comment représenter les étapes dans votre application avec le modèle mémento / commande? Tout d'abord, je ne sais pas exactement comment votre application fonctionne, mais j'espère que vous verrez où je veux en venir. Dites que je veux placer un ClassNode sur le diagramme avec les propriétés suivantes

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

Cela serait présenté comme un objet de commande. Disons que cela va à un DiagramController. Ensuite, la responsabilité du contrôleur de diagramme peut être d’enregistrer cette commande (le pousser sur une pile serait mon pari) et de transmettre la commande à un DiagramBuilder par exemple. DiagramBuilder serait en fait responsable de la mise à jour de l'affichage.

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);
  }
}

Quelque chose comme ça devrait le faire et bien sûr, il y aura beaucoup de détails à régler. Soit dit en passant, plus vos nœuds ont de propriétés, plus Undraw devra être détaillé.

L’utilisation d’un identifiant pour lier la commande de votre pile à l’élément dessiné peut être une bonne idée. Cela pourrait ressembler à ceci:

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);
  }
} 

À ce stade, vous ne devez absolument pas disposer de l'objet puisque vous avez l'identifiant, mais cela vous sera utile si vous décidez également d'implémenter la fonctionnalité de restauration. Un bon moyen de générer l'identifiant de vos noeuds consiste à implémenter une méthode de hachage pour ceux-ci, à l'exception du fait qu'il ne vous sera pas garanti de ne pas dupliquer vos noeuds de manière à rendre le code de hachage identique.

La partie suivante du problème se trouve dans votre DiagramBuilder car vous essayez de comprendre comment gérer correctement ces commandes. Pour cela, tout ce que je peux dire, c’est d’assurer que vous pouvez créer une action inverse pour chaque type de composant que vous pouvez ajouter. Pour gérer la dissociation, vous pouvez consulter la propriété de connexion de bord (des liens dans votre code, je pense) et informer chacune des connexions de bord qu'elles doivent se déconnecter du nœud spécifique. Je suppose qu’en cas de déconnexion, ils pourraient se redessiner de manière appropriée.

Pour résumer un peu, je vous recommande de ne pas conserver de référence à vos noeuds dans la pile, mais simplement une sorte de jeton qui représente l'état d'un noeud donné à ce stade. Cela vous permettra de représenter le même nœud dans votre pile d'annulation à plusieurs endroits sans qu'il fasse référence au même objet.

Publiez si vous avez des questions. C'est un problème complexe.

Autres conseils

À mon humble avis, vous y réfléchissez de manière plus compliquée qu’elle ne l’est réellement. Afin de revenir à l'état précédent, le clone de tout le nœud n'est pas requis du tout. Chaque classe de commande * * aura plutôt -

.
  1. référence au noeud sur lequel il agit,
  2. objet mémento (avoir les variables d'état juste assez pour que le nœud puisse y revenir)
  3. méthode execute ()
  4. méthode undo ().

Les classes de commande faisant référence au noeud, nous n'avons pas besoin du mécanisme d'ID pour faire référence aux objets du diagramme.

Dans l'exemple de votre question, nous souhaitons déplacer un nœud vers un nouvel emplacement. Pour cela, nous avons une 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
    }
}
  
    

Qu'en est-il des liens? Ils devraient également pouvoir être déplacés, mais je ne souhaite pas créer de commande uniquement pour les liens (et une pour les nœuds).

  

J'ai lu dans le livre GoF (dans la discussion sur le modèle mémento) que le déplacement d'un lien avec le changement de position des nœuds est géré par une sorte de résolveur de contraintes.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top