为了使软件的使用更加人性化,对于误操作,我们需要提供一种类似“后悔药”的机制,让软件系统可以回到误操作前的状态,因此需要保存用户每一次操作时系统的状态,一旦出现误操作,可以把存储的历史状态取出即可回到之前的状态。
ü现在大多数软件都有撤销(Undo)的功能,快捷键一般都是Ctrl+Z,目的就是为了解决这个后悔的问题。
定义:
备忘录模式(Memento Pattern):在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。它是一种对象行为型模式,其别名为Token。
Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later.
Originator:原发器: 原发器起可以创建一个备忘录,并存储它当前的内部状态,也可以使用备忘录来恢复其内部状态。一般将需要保存内部状态的类设计为原发器,如一个存储用于信息或商品信息的对象。
Memento:备忘录,存储原发器的内部状态,根据原发器来决定保存哪些内部状态。备忘录的设计一般可以参考原发器的设计,根据实际需要确定备忘录的属性。需要注意的是,除了原发器本身与负责人类之外,备忘录对象不能直接供其他类使用,因此原发器的设计在不同的编程语言中实现机制有所不同。
Caretaker:负责人,又称管理者,他负责保存备忘录,但是不能对备忘录的内容进行操作或检查,在负责人类中可以存储一个或多个备忘录对象,它只负责存储对象,而不能修改对象,也无需知道对象的实现细节。
模式分析:
理解备忘录模式并不难,但是关键在于如何设计备忘录类和负责人类,
由于在备忘录中存储的是原发器的中间状态,因此需要防止原发器以外的其他对象访问备忘录。
备忘录对象通常封装了原发器的部分或所有的状态信息,而且这些状态不能被其他对象访问,也就是说不能在备忘录对象之外保存原发器状态,因为暴露其内部状态将违反封装的原则,可能有损系统的可靠性和可扩展性。
ü为了实现对备忘录对象的封装,需要对备忘录的调用进行控制:
•对于原发器而言,它可以调用备忘录的所有信息,允许原发器访问返回到先前状态所需的所有数据;
•对于负责人而言,只负责备忘录的保存并将备忘录传递给其他对象;
•对于其他对象而言,只需要从负责人处取出备忘录对象并将原发器对象的状态恢复,而无须关心备忘录的保存细节。
ü理想的情况是只允许生成该备忘录的那个原发器访问备忘录的内部状态。
典型原发器代码:
package dp.memento; public class Originator { private String state; public Originator(){} // 创建一个备忘录对象 public Memento createMemento(){ return new Memento(this); } // 根据备忘录对象恢复原发器状态 public void restoreMemento(Memento m){ state = m.state; } public void setState(String state) { this.state=state; } public String getState() { return this.state; } }
package dp.memento; class Memento { private String state; public Memento(Originator o){ state = o.state; } public void setState(String state) { this.state=state; } public String getState() { return this.state; } }
在设计备忘录类时要考虑其封装性,除了originator类,不允许其他类来调用其构造函数与相关方法,如果不考虑其封装性,允许其他类调用setState()等方法,将导致在备忘录中保存的历史状态 发生改变,通过撤销操作所回复的状态就不再是真实的历史状态,备忘录模式也就失去了意义。
在使用java语言实现备忘录模式时,一般通过将Memento类与originator类定义在同一个package中来实现封装,在java中可使用默认访问标识符来定义Memento类,即保证其保内可见性。只有Originator类可以对其进行访问,而限制其他类对Memento的访问,
负责人类:
package dp.memento; public class Caretaker { private Memento memento; public Memento getMemento() { return memento; } public void setMemento(Memento memento) { this.memento=memento; } }
在Caretaker中也不应该直接调用Memento中的状态方法,它的作用仅仅用于存储备忘录对象,。
ü实例:用户信息操作撤销
•某系统提供了用户信息操作模块,用户可以修改自己的各项信息。为了使操作过程更加人性化,现使用备忘录模式对系统进行改进,使得用户在进行了错误操作之后可以恢复到操作之前的状态。
Originator:
package demo1.memento; public class UserInfoDTO { private String account; private String password; private String telNo; public Memento createMemento() { return new Memento(this); } public void restoreMemento(Memento memento) { this.account=memento.getAccount(); this.password=memento.getPassword(); this.telNo=memento.getTelNo(); } public void printInfo() { System.out.println("Account:"+this.account); System.out.println("Password:"+this.password); System.out.println("TelNO:"+this.telNo); } public String getAccount() { return account; } public void setAccount(String account) { this.account = account; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getTelNo() { return telNo; } public void setTelNo(String telNo) { this.telNo = telNo; } }
Memento:
package demo1.memento; public class Memento { private String account; private String password; private String telNo; public Memento(UserInfoDTO o) { account=o.getAccount(); password=o.getPassword(); telNo=o.getTelNo(); } public String getAccount() { return account; } public void setAccount(String account) { this.account = account; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getTelNo() { return telNo; } public void setTelNo(String telNo) { this.telNo = telNo; } }
caretaker:
public class Caretaker { private Memento memento; public Memento getMemento() { return this.memento; } public void setMemento(Memento memento) { this.memento=memento; } }
客户端代码:
import demo1.memento.Caretaker; import demo1.memento.UserInfoDTO; public class Client { public static void main(String[] args) { UserInfoDTO o=new UserInfoDTO(); Caretaker c=new Caretaker();//创建负责人 o.setAccount("user1"); o.setPassword("pwd1"); o.setTelNo("tel1"); o.printInfo(); c.setMemento(o.createMemento()); //保存备忘录 System.out.println("-------------------"); o.setAccount("user2"); o.printInfo(); } }
Account:user1
Password:pwd1
TelNO:tel1
-------------------
Account:user2
Password:pwd1
TelNO:tel1
恢复---------------------------
Account:user1
Password:pwd1
TelNO:tel1
为什么我们要创建Caretaker类?参考前面所说的。
不创建Caretaker同样可以操作:
UserInfoDTO o=new UserInfoDTO(); o.setAccount("user1"); o.setPassword("pwd1"); o.setTelNo("tel1"); o.printInfo(); Memento memento=o.createMemento(); System.out.println("改变后:"); o.setAccount("user2"); o.printInfo(); o.restoreMemento(memento); System.out.println("恢复后为:"); o.printInfo();