纪念品模式的缺点
Memento pattern drawbacks
所以,这是 Memento 模式的典型实现(跳过 getter 和 setter)。
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());
}
}
我发现此模式的所有实现都或多或少与上面的相同。但是这种实现有一些问题,我不喜欢:
- 可以同时触发
employee.revert()
和 caretaker.revert(employee)
。我希望只有一个接入点。
- 如果我们想要更改 EmployeeMemento,我们还必须在 Employee class 中进行更改(因为
revert
方法)。
有办法克服这个问题吗?
又或者是我太在意了,这个细节不是那么重要?
1) 请注意,Caretaker 应该负责保管 Mementos,不一定负责 Undo/Redo。如果您查看 Internet 上的各种实现(例如 here),您会发现 Caretaker 没有 revert()
,但通常类似于 getMemento()
。所以负责撤消的 class 是另一个人,它在 Caretaker 上调用 getMemento()
,然后在 Subject 上调用 revert()
。
即使您希望 Caretaker 负责撤消操作,请注意 employee.revert()
是一种专为 caretaker.revert()
使用而创建的方法,因为在此设计中,没有其他人可以访问纪念品。您可以将其可见性降低为仅由 Caretaker 可见。 (如果这是 C++,使用 friend
可以轻松完成,但在 Java 中,您必须发挥创意并使用 package
可见性或其他方式。)
2) 在 Memento 模式中,class 和它的 Memento 是紧耦合的。实际上只有 class 本身可以访问 Memento 的内部结构,没有其他人应该看到 Memento 是如何组成的。因此,对 class 的更改是否会传播到其 Memento.
并不重要
然后再来一次 如果你想隔离变化,你可以再次发挥创意。例如,不是在 Class 和它的 Memento 中复制 name
和 phone
,你可以提取另一个包含这些字段的 class(假设名称为 State
) 然后在原来的 class 和它的 Memento 中使用这个 State
。这样,当您更改 class 的状态时,您只需要修改 State
.
旁注:最好将 Memento 定义为主题内的嵌套静态 class。
所以我的设计解决了您的问题,应该是这样的:
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());
}
}
所以,这是 Memento 模式的典型实现(跳过 getter 和 setter)。
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());
}
}
我发现此模式的所有实现都或多或少与上面的相同。但是这种实现有一些问题,我不喜欢:
- 可以同时触发
employee.revert()
和caretaker.revert(employee)
。我希望只有一个接入点。 - 如果我们想要更改 EmployeeMemento,我们还必须在 Employee class 中进行更改(因为
revert
方法)。
有办法克服这个问题吗? 又或者是我太在意了,这个细节不是那么重要?
1) 请注意,Caretaker 应该负责保管 Mementos,不一定负责 Undo/Redo。如果您查看 Internet 上的各种实现(例如 here),您会发现 Caretaker 没有 revert()
,但通常类似于 getMemento()
。所以负责撤消的 class 是另一个人,它在 Caretaker 上调用 getMemento()
,然后在 Subject 上调用 revert()
。
即使您希望 Caretaker 负责撤消操作,请注意 employee.revert()
是一种专为 caretaker.revert()
使用而创建的方法,因为在此设计中,没有其他人可以访问纪念品。您可以将其可见性降低为仅由 Caretaker 可见。 (如果这是 C++,使用 friend
可以轻松完成,但在 Java 中,您必须发挥创意并使用 package
可见性或其他方式。)
2) 在 Memento 模式中,class 和它的 Memento 是紧耦合的。实际上只有 class 本身可以访问 Memento 的内部结构,没有其他人应该看到 Memento 是如何组成的。因此,对 class 的更改是否会传播到其 Memento.
并不重要然后再来一次 如果你想隔离变化,你可以再次发挥创意。例如,不是在 Class 和它的 Memento 中复制 name
和 phone
,你可以提取另一个包含这些字段的 class(假设名称为 State
) 然后在原来的 class 和它的 Memento 中使用这个 State
。这样,当您更改 class 的状态时,您只需要修改 State
.
旁注:最好将 Memento 定义为主题内的嵌套静态 class。
所以我的设计解决了您的问题,应该是这样的:
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());
}
}