Сохранение состояния сложного объекта с помощью шаблона Memento (и Command) - PullRequest
4 голосов
/ 17 октября 2008

Я работаю над небольшим проектом UML-редактора на Java, который я начал пару месяцев назад. Через несколько недель я получил рабочую копию для редактора диаграмм классов UML.

Но теперь я полностью переработал его для поддержки других типов диаграмм, таких как последовательность, состояние, класс и т. Д. Это делается путем реализации структуры построения графиков (меня очень вдохновляет работа Кей Хорстманн над тема с помощью Violet UML editor).

Редизайн шел гладко, пока один из моих друзей не сказал мне, что я забыл добавить функциональность Do / Undo в проект, что, на мой взгляд, жизненно важно.

Вспоминая курсы объектно-ориентированного дизайна, я сразу подумал о шаблоне Memento и Command.

Вот сделка. У меня есть абстрактный класс AbstractDiagram, который содержит два ArrayLists: один для хранения узлов (называемых элементами в моем проекте), а другой для хранения Edges (называемых ссылками в моих проектах). Диаграмма, вероятно, будет содержать стек команд, которые можно отменить / повторить. Довольно стандартный.

Как я могу эффективно выполнить эти команды? Скажем, например, что я хочу переместить узел (узел будет иметь тип интерфейса с именем INode, и из него будут получены конкретные узлы (ClassNode, InterfaceNode, NoteNode и т. Д.)).

Информация о положении хранится как атрибут в узле, поэтому, изменяя этот атрибут в самом узле, состояние изменяется. Когда дисплей будет обновлен, узел переместится. Это часть Мементо паттерна (я думаю), с той разницей, что объектом является само состояние.

Более того, если я сохраню клон исходного узла (до его перемещения), я смогу вернуться к его старой версии. Тот же метод применяется к информации, содержащейся в узле (имя класса или интерфейса, текст для узла заметки, имя атрибута и т. Д.).

Дело в том, как заменить на схеме узел с его клоном при операции отмены / возврата? Если я клонирую исходный объект, на который ссылается диаграмма (находящийся в списке узлов), клон не является ссылкой на диаграмме, и единственное, на что он указывает, это сама команда! Должны ли я включить в диаграмму механизмы для нахождения узла по идентификатору (например), чтобы я мог заменить на диаграмме узел его клоном (и наоборот)? Это до шаблонов Memento и Command, чтобы сделать это? А как насчет ссылок? Они также должны быть подвижными, но я не хочу создавать команду только для ссылок (и одну только для узлов), и я должен иметь возможность изменять правильный список (узлы или ссылки) в соответствии с типом объекта команды имеет в виду.

Как бы вы поступили? Короче говоря, у меня возникают проблемы с представлением состояния объекта в шаблоне команда / памятка, чтобы его можно было эффективно восстановить и восстановить исходный объект в списке диаграмм, в зависимости от типа объекта (узла или ссылки).

Большое спасибо!

Гийом.

П.С .: Если мне неясно, скажите мне, и я уточню свое сообщение (как всегда!).

Редактировать

Вот мое реальное решение, которое я начал реализовывать до публикации этого вопроса.

Во-первых, у меня есть класс AbstractCommand, определенный следующим образом:

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

Затем каждый тип команды реализуется с использованием конкретного вывода AbstractCommand.

Итак, у меня есть команда для перемещения объекта:

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

У меня также есть MoveRemoveCommand (для ... перемещения или удаления объекта / узла). Если я использую идентификатор метода instanceof, мне не нужно передавать диаграмму фактическому узлу или ссылке, чтобы она могла удалить себя из диаграммы (я думаю, что это плохая идея).

Абстрактная диаграмма диаграммы; Добавляемый объект; Тип 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;
        }
    }
}

Наконец, у меня есть ModificationCommand, который используется для изменения информации об узле или ссылке (имя класса и т. Д.). Это может быть объединено в будущем с MoveCommand. Этот класс пока пуст. Я, вероятно, сделаю идентификацию с механизмом, чтобы определить, является ли измененный объект узлом или ребром (через instanceof или специальное определение в идентификаторе).

Это хорошее решение?

Ответы [ 2 ]

4 голосов
/ 17 октября 2008

Я думаю, вам просто нужно разложить вашу проблему на более мелкие.

Первая проблема: Q: Как представить шаги в вашем приложении с помощью шаблона memento / command? Прежде всего, я понятия не имею, как именно работает ваше приложение, но, надеюсь, вы поймете, куда я иду с этим. Скажем, я хочу разместить ClassNode на диаграмме со следующими свойствами

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

Это будет обернуто как объект команды. Скажите, что идет к DiagramController. Тогда ответственность за управление диаграммой может заключаться в том, чтобы записать эту команду (моя ставка была бы в стеке) и передать ее, например, DiagramBuilder. DiagramBuilder будет фактически отвечать за обновление дисплея.

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

Что-то подобное должно сделать это, и, конечно, будет много деталей, чтобы разобраться. Кстати, чем больше свойств у ваших узлов, тем более детальным будет Undraw.

Использование id для связывания команды в вашем стеке с нарисованным элементом может быть хорошей идеей. Это может выглядеть так:

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

На данный момент вам совершенно необязательно иметь объект, поскольку у вас есть идентификатор, но это будет полезно, если вы решите также реализовать функцию повтора. Хороший способ создать идентификатор для ваших узлов - это реализовать для них метод хеш-кода, за исключением того факта, что вы не гарантируете, что не будете дублировать ваши узлы таким образом, что хеш-код будет идентичен.

Следующая часть проблемы находится внутри вашего DiagramBuilder, потому что вы пытаетесь понять, как, черт возьми, справляться с этими командами. Для этого все, что я могу сказать, это просто убедиться, что вы можете создать обратное действие для каждого типа компонента, который вы можете добавить. Чтобы справиться с разделением, вы можете взглянуть на свойство пограничного соединения (я думаю, что это ссылки в вашем коде) и уведомить каждое из пограничных соединений о том, что они должны отключиться от определенного узла. Я бы предположил, что при отключении они могли бы перерисовать себя соответствующим образом.

Чтобы подвести итог, я бы порекомендовал не хранить ссылку на ваши узлы в стеке, а вместо этого - просто вид токена, который представляет состояние данного узла в этой точке. Это позволит вам представлять один и тот же узел в стеке отмены в нескольких местах, не ссылаясь на один и тот же объект.

Опубликовать, если у вас есть вопросы. Это сложный вопрос.

1 голос
/ 02 мая 2011

По моему скромному мнению, вы думаете об этом сложнее, чем на самом деле. Чтобы вернуться в предыдущее состояние, клонирование целого узла не требуется вообще. Скорее каждый ** Командный класс будет иметь -

  1. ссылка на узел, на который он действует,
  2. объект памятки (с переменными состояния, достаточными для возврата узла)
  3. метод execute ()
  4. метод отмены ().

Поскольку классы команд имеют ссылку на узел, нам не нужен механизм идентификатора для ссылки на объекты на диаграмме.

В примере из вашего вопроса мы хотим переместить узел на новую позицию. Для этого у нас есть класс 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
    }
}

А как насчет ссылок? Они также должны быть подвижными, но я не хочу создавать команду только для ссылок (и одну только для узлов).

Я прочитал в книге GoF (в обсуждении паттерна сувенира), что перемещение ссылки с изменением положения узлов обрабатывается каким-либо средством решения ограничений.

...