Недостатки модели Memento

Итак, вот типичная реализация паттерна Memento (геттеры и сеттеры пропущены).

public class Employee {
    private String name;
    private String phone;

    public EmployeeMemento save() {
        return new EmployeeMemento(name, phone);
    }

    public void revert(EmployeeMemento memento) {
        this.name = memento.getName();
        this.phone = memento.getPhone();
    }
}


  public class EmployeeMemento {
        private final String name;
        private final String phone;

        public EmployeeMemento(String name, String phone) {
            this.name = name;
            this.phone = phone;
        }
    }

public class Caretaker {
    private Stack<EmployeeMemento> history;

    public Caretaker() {
        history = new Stack<>();
    }

    public void save(Employee employee) {
        history.push(employee.save());
    }

    public void revert(Employee employee) {
        employee.revert(history.pop());
    }
}

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

  1. Можно активировать как employee.revert(), так и caretaker.revert(employee). Я хотел бы иметь только одну точку доступа.
  2. Если мы хотим изменить EmployeeMemento, мы также должны внести изменения в класс Employee (из-за метода revert).

Есть ли способ преодолеть это? А может я слишком много внимания уделяю, и эти детали не так важны?


person Ivan Nikolaychuk    schedule 04.06.2016    source источник


Ответы (1)


1) Обратите внимание, что смотритель должен заботиться о хранении сувениров, а не об отмене/возврате. Если вы посмотрите на различные реализации в Интернете (например, здесь), вы увидите что Смотритель не имеет revert(), но обычно что-то вроде getMemento(). Таким образом, класс, который занимается Отменой, — это кто-то другой, который вызывает getMemento() для Опекуна, а затем revert() для Субъекта.

Даже если вы хотите, чтобы Опекун позаботился об Отмене, учтите, что employee.revert() — это метод, созданный исключительно для использования caretaker.revert(), потому что в этом дизайне ни у кого больше нет доступа к Мементо. Вы можете уменьшить его видимость, чтобы его мог видеть только смотритель. (Если бы это был C++, это было бы легко сделать с помощью friend, но в Java вы должны проявить творческий подход и использовать видимость package или каким-то другим способом.)

2) В шаблоне Memento класс и его Memento тесно связаны. На самом деле только сам класс имеет доступ к внутренностям Memento, и никто другой не должен видеть, как устроен Memento. Так что не имеет значения, если изменение класса распространяется на его память.

Затем снова. Если вы хотите изолировать изменения, вы можете снова проявить творческий подход. Например, вместо того, чтобы дублировать name и phone как в классе, так и в его сувенире, вы можете извлечь другой класс, который содержит эти поля (скажем, по имени State), а затем использовать этот State как в исходном классе, так и в его сувенире. Таким образом, когда у вас есть изменения в состоянии класса, вам нужно только изменить State.

Примечание. Лучше определить Memento как вложенный статический класс внутри Subject.

Итак, мой дизайн, который решает ваши проблемы, будет примерно таким:

public class Employee {
    private State state;

    public Memento save() {
        return new Memento(state);
    }

    public void revert(Memento memento) {
        this.state = memento.state;
    }

    public static class Memento {
        private final State state;

        public Memento(State state) {
            this.state = state;
        }
    }

    public static class State {
        private String name;
        private String phone;
    }
}

public class Caretaker {
    private Stack<Employee.Memento> history;

    public Caretaker() {
        history = new Stack<>();
    }

    public void addMemento(Employee.Memento memento) {
        history.push(memento);
    }

    public Employee.Memento getMemento() {
        return history.pop();
    }
}

public class UndoHandler {
    Employee employee;
    Caretaker caretaker;

    public void snapshot() {
        caretaker.save(employee.save());
    }

    public void undo() {
        employee.revert(caretaker.getMemento());
    }
}
person Mousa    schedule 04.06.2016