Pergunta

Eu estou trabalhando em um projeto editor UML pequeno, em Java, que eu comecei um par de meses atrás. Depois de algumas semanas, recebi uma cópia de trabalho para um editor de diagrama de classe UML.

Mas agora, estou redesenhando-o completamente para suportar outros tipos de diagramas, uma seqüência tal, estado, classe, etc. Isto é feito através da implementação de uma estrutura de construção de gráfico (estou muito inspirado por Cay Horstmann trabalho no assunto com o editor Violet UML).

Redesign estava indo bem até que um dos meus amigos me disse que eu esqueci de adicionar um Funcionalidades de Do / Undo para o projeto, que, na minha opinião, é vital.

Lembrando cursos de design orientado a objeto, pensei imediatamente em Memento e Comando padrão.

Aqui está o negócio. Eu tenho uma classe abstrata, AbstractDiagram, que contém dois ArrayLists: um para armazenar os nós (chamados de elementos no meu projeto) e outra para armazenar Edges (chamado de links em meus projetos). O diagrama provavelmente vai manter uma pilha de comandos que podem ser Undoed / Redoed. Belos padrão.

Como posso executar esses comandos de forma eficiente? Digamos, por exemplo, que eu quero passar um nó (o nó será um tipo de interface chamado INode, e haverá nós concretas derivadas dela (ClassNode, InterfaceNode, NoteNode, etc.)).

As informações posição é mantida como um atributo no nó, por isso, modying esse atributo no próprio nó, o estado é alterado. Quando a tela será atualizada, o nó terá movido. Esta é a parte Memento do padrão (eu acho), com a diferença de que o objeto é o próprio Estado.

Além disso, se eu mantiver um clone do nó original (antes de ser transferida), eu possa voltar à sua antiga versão. A mesma técnica se aplica para as informações contidas no nó (o nome da classe ou interface, o texto para um nó nota, o nome de atributos, e assim por diante).

A coisa é, como faço para substituir, no diagrama, o nó com o seu clone mediante operação de desfazer / refazer? Se eu clonar o objeto original que é referenciado pelo diagrama (sendo na lista de nós), o clone não é referência no diagrama, e a única coisa que aponta para o próprio comando! Shoud I incluem mecanismos no diagrama para encontrar um nó de acordo com uma identificação (por exemplo) de modo que pode substituir, no diagrama, o nó pelo seu clone (e vice-versa)? É até os padrões de Memento e comando para fazer isso? E sobre links? Eles devem ser móveis também, mas eu não quero criar um comando apenas para ligações (e um só para nós), e que eu deveria ser capaz de modificar a lista da direita (nós ou links) de acordo com o tipo do objeto de comando está se referindo.

Como você procederia? Em suma, eu estou tendo problemas para representar o estado de um objeto em um padrão de comando / memento de modo que possa ser eficientemente recuperada e o objeto original restaurada na lista diagrama, e, dependendo do tipo de objeto (nó ou link).

Muito obrigado!

Guillaume.

P.S .: se eu não sou claro, me diga e eu vou esclarecer a minha mensagem (como sempre!).

Editar

Aqui está a minha solução real, que começou a implementar antes de postar esta questão.

Em primeiro lugar, eu tenho uma classe AbstractCommand definido como a seguir:

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

Em seguida, cada tipo de comando é implementado utilizando uma derivação concreta de AbstractCommand.

Então, eu tenho um comando para mover um objeto:

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

Eu também tenho um MoveRemoveCommand (para ... mover ou remover um objeto / nó). Se eu usar o ID do método instanceof, eu não tenho para passar o diagrama para o nó actual ou link para que ele possa retirar-se a partir do diagrama (que é uma má ideia eu acho).

AbstractDiagram diagrama; obj addable; AddRemoveType tipo;

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

Finalmente, tenho um ModificationCommand que é usado para modificar a informação de um (nome da classe, etc.) nó ou link. Isso pode ser fundidos no futuro com o movimentoComando. Esta classe está vazia por agora. Provavelmente, vou fazer a coisa ID com um mecanismo para determinar se o objeto modificado é um nó ou uma borda (via instanceof ou um denotion especial na ID).

É esta é uma boa solução?

Foi útil?

Solução

Eu acho que você só precisa decompor o problema em partes menores.

Primeiro problema: Q: Como representar as etapas em seu aplicativo com o padrão memento / comando? Primeiro, eu não tenho nenhuma idéia exatamente como o aplicativo funciona, mas esperamos que você vai ver onde eu estou indo com isso. Digamos que eu queira colocar um ClassNode no diagrama que, com as seguintes propriedades

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

Isso seria embrulhado como um objeto de comando. Dizer que vai para um DiagramController. Em seguida, a responsabilidade do controlador de diagrama pode ser para gravar esse comando (push para uma pilha seria minha aposta) e passar o comando para um DiagramBuilder por exemplo. O DiagramBuilder seria realmente responsável por atualizar a exibição.

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

Alguns coisa como essa deve fazê-lo e, claro, haverá uma abundância de detalhes para resolver. By the way, o mais Propriedades seus nós temos a descerrar mais detalhado vai ter que ser.

Usando um ID para ligar o comando em sua pilha para o elemento desenhado pode ser uma boa idéia. Aquele olhar poder assim:

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

Neste ponto, você não fazer absolutamente tem que ter o objeto desde que você tem o ID, mas ele vai ser útil se você decidir também implementar a funcionalidade refazer. Uma boa maneira de gerar o id para seus nós seria implementar um método hashcode para eles, exceto para o fato de que você não estaria garantida para não duplicar os nós de tal forma que faria com que o código hash para ser idênticos.

A próxima parte do problema está dentro do seu DiagramBuilder porque você está tentando descobrir como o diabo para lidar com esses comandos. Por que tudo que eu posso dizer é realmente apenas garantir que você pode criar uma ação inversa para cada tipo de componente que você pode adicionar. Para lidar com a desvinculação você pode olhar para a propriedade beira-ligação (links em seu código eu acho) e notificar cada um dos Edge-conexões que eles estão a desconexão do nó específico. Eu diria que em desconexão eles poderiam redesenhar-se adequadamente.

Para meio resumir, eu recomendo não manter uma referência para os seus nódulos na pilha, mas ao invés de apenas uma espécie de símbolo que representa o estado de um determinado nó nesse ponto. Isso permitirá que você para representar o mesmo nó em sua pilha de desfazer em vários lugares sem ele referindo-se ao mesmo objeto.

Depois, se você tem Q de. Esta é uma questão complexa.

Outras dicas

Na minha humilde opinião, você está pensando isso de uma maneira mais complicado do que realmente é. Para reverter para o estado anterior, clone do nó inteiro não é necessária em todos. Em vez de cada * * classe Command terá -

  1. referência ao nó que está agindo em cima,
  2. objeto memento (com variáveis ??de estado apenas o suficiente para o nó para reverter a)
  3. método execute ()
  4. undo () método.

Uma vez que as classes de comando têm referência ao nó, nós não precisamos de mecanismo de ID para se referir a objetos no diagrama.

No exemplo da sua pergunta, queremos mover um nó para uma nova posição. Para isso, temos uma 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 sobre links? Eles devem ser móveis também, mas eu não quero criar um comando apenas para ligações (e um só para nós).

Eu li no GoF livro (em discussão padrão memento) que se movem de ligação com a mudança de posição dos nós são manipulados por algum tipo de solver restrição.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top