鸣谢:《大话设计模式》
简介
行为型设计模式,用于捕获一个对象的内部状态,将其保存在一个外部对象中,并在以后可以将对象恢复到原始状态。备忘录模式允许在不破坏对象封装性的情况下,保存和恢复对象的状态。
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
主要组成部分:
- ■ Originator(发起人):负责创建一个备忘录Memento,用以记录当前时刻它的内部状态,并可使用备忘录恢复内部状态。Originator可根据需要决定Memento存储Originator的哪些内部状态。
-
■ Memento(备忘录):负责存储Originator对象的内部状态,并可防止Originator以外的其他对象访问备忘录Memento。备忘录有两个接口,Caretaker只能看到备忘录的窄接口,它只能将备忘录传递给其他对象。Originator能够看到一个宽接口,允许它访问返回到先前状态所需的所有数据。
-
■ Caretaker(管理者):负责保存好备忘录Memento,不能对备忘录的内容进行操作或检查。
采摘自《大话设计模式》
工作流程
- 发起者创建一个备忘录对象,并将其状态存储在备忘录中。
-
发起者可以将备忘录交给管理者,以便后续恢复状态。
-
当需要恢复状态时,管理者将备忘录交还给发起者,并发起者使用备忘录中的状态来还原其内部状态。
例子1:游戏存档
《大话设计模式》书中的例子,感觉差点意思,推荐看例子2
结构
代码
发起者(Originator)
/**
* @author: lpy
* @Date: 2023/10/27
*/
@NoArgsConstructor
@AllArgsConstructor
public class GameRole {
private String name;
private String bloodVolume;
private String schedule;
public GameRoleMemento saveToMemento(){
return new GameRoleMemento(name,bloodVolume,schedule);
}
public void undoFromMemento(GameRoleMemento gm){
this.name = gm.getName();
this.bloodVolume = gm.getBloodVolume();
this.schedule = gm.getSchedule();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getBloodVolume() {
return bloodVolume;
}
public void setBloodVolume(String bloodVolume) {
this.bloodVolume = bloodVolume;
}
public String getSchedule() {
return schedule;
}
public void setSchedule(String schedule) {
this.schedule = schedule;
}
}
备忘录(Memento)
/**
* @author: lpy
* @Date: 2023/10/27
* @desc: 注意,他是一个窄接口,不需要set方法,他是快照
*/
@AllArgsConstructor
public class GameRoleMemento {
private String name;
private String bloodVolume;
private String schedule;
public String getName() {
return name;
}
public String getBloodVolume() {
return bloodVolume;
}
public String getSchedule() {
return schedule;
}
@Override
public String toString() {
return "GameRoleMemento{" +
"name='" + name + '\'' +
", bloodVolume='" + bloodVolume + '\'' +
", schedule='" + schedule + '\'' +
'}';
}
}
管理者(Caretaker)
/**
* @author: lpy
* @Date: 2023/10/27
*/
public class GameRoleManager {
private GameRoleMemento gameRoleMemento;
public void addMemento(GameRoleMemento gm){
this.gameRoleMemento = gm;
}
public GameRoleMemento getGameRoleMemento(){
return gameRoleMemento;
}
}
客户端测试类
/**
* @author: lpy
* @Date: 2023/10/27
*/
public class GameMementoTest {
public static void main(String[] args) {
GameRoleManager manager = new GameRoleManager();
GameRole siko = new GameRole("赛克", "100", "第三关");
GameRoleMemento gameRoleMemento = siko.saveToMemento();
manager.addMemento(gameRoleMemento);
System.out.println("第一次:"+gameRoleMemento.toString());
siko.setSchedule("第四关");
siko.setBloodVolume("90");
gameRoleMemento = manager.getGameRoleMemento();
siko.undoFromMemento(gameRoleMemento);
System.out.println("第二次:"+gameRoleMemento.toString());
siko.setSchedule("第四关");
siko.setBloodVolume("95");
gameRoleMemento = siko.saveToMemento();
manager.addMemento(gameRoleMemento);
System.out.println("第三次:"+gameRoleMemento.toString());
}
}
例子2:文章存档
慕课视频《Java设计模式精讲 Debug方式+内存分析》的例子
结构
代码
发起者(Originator)
/**
* @author: lpy
* @Date: 2023/10/27
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Article {
private String title;
private String author;
private String content;
/*------------------------核心方法-------------------------*/
public ArticleMemento saveToMemento(){
return new ArticleMemento(title, author, content);
}
public void undoFromMemento(ArticleMemento am){
this.author = am.getAuthor();
this.title = am.getTitle();
this.content = am.getContent();
}
/*-------------------------------------------------*/
@Override
public String toString() {
return "Article{" +
"title='" + title + '\'' +
", author='" + author + '\'' +
", content='" + content + '\'' +
'}';
}
}
备忘录(Memento)
* @author: lpy
* @Date: 2023/10/27
* @desc: 注意memento是窄接口,只提供访问,不提供修改,在《大话设计模式》里有提及
*/
@AllArgsConstructor
public class ArticleMemento {
private String title;
private String author;
private String content;
public String getTitle() {
return title;
}
public String getAuthor() {
return author;
}
public String getContent() {
return content;
}
@Override
public String toString() {
return "ArticleMemento{" +
"title='" + title + '\'' +
", author='" + author + '\'' +
", content='" + content + '\'' +
'}';
}
}
管理者(Caretaker)
/**
* @author: lpy
* @Date: 2023/10/27
*/
public class ArticleManager {
private static final Stack<ArticleMemento> as = new Stack<>();
public void addArticleMemento(ArticleMemento at){
as.push(at);
}
public ArticleMemento getArticleMemento(){
return as.pop();
}
}
客户端测试类
/**
* @author: lpy
* @Date: 2023/10/27
*/
public class ArticleTest {
public static void main(String[] args) {
ArticleManager manager = new ArticleManager();
Article article = new Article("文章A", "xiaosheng", "简单工厂模式");
ArticleMemento memento = article.saveToMemento();
manager.addArticleMemento(memento);
System.out.println("快照1:"+article.toString());
article.setTitle("文章B");
article.setContent("外观模式");
memento = article.saveToMemento();
manager.addArticleMemento(memento);
System.out.println("快照2:"+article.toString());
article.setTitle("文章C");
article.setContent("状态模式");
memento = article.saveToMemento();
manager.addArticleMemento(memento);
System.out.println("快照3:"+article.toString());
article.setTitle("摆烂了");
memento = manager.getArticleMemento();
article.undoFromMemento(memento);
System.out.println("恢复1:"+article.toString());
article.setTitle("摆烂了2");
memento = manager.getArticleMemento();
article.undoFromMemento(memento);
System.out.println("恢复2:"+article.toString());
}
}
应用
文本编辑器、绘图工具、游戏等。它提供了一种可维护的方式来管理对象状态的变化。
spring-webflow源码中 StateManageableMessageContext接口的createMessagesMemento、restoreMessages方法
备忘录模式优缺点
- 优点:
- 封装性:备忘录模式将状态保存和恢复的逻辑封装在备忘录对象中,可以防止外部对象直接访问或修改状态,增加了封装性。
-
状态历史:备忘录模式允许发起者保存多个状态的历史记录,以便在需要时恢复到不同的状态。
-
支持撤销和恢复:备忘录模式使应用程序能够支持撤销和恢复功能,因为可以保存对象在不同时间点的状态。
-
缺点:
- 内存消耗增加
7 大设计原则
不能全部遵守 , 也不能不遵守 , 注意平衡 功能 和 系统复杂度 , 找到最合适的一个点 ;
- 单一职责原则(Single Responsibility Principle - SRP):
这个原则规定一个类应该只有一个改变的理由,也就是说,一个类应该只有一个单一的职责。这有助于保持类的简单性和可维护性,因为每个类只需关注一个特定的功能。
- 开放-封闭原则(Open-Closed Principle - OCP):
开放-封闭原则要求系统中的软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。这意味着当需要添加新功能时,应该通过扩展现有代码来实现,而不是修改原有代码。
- 里氏替换原则(Liskov Substitution Principle - LSP):
里氏替换原则规定,子类应该能够替换其父类,而不会影响程序的正确性。也就是说,子类应该继承父类的行为,但可以扩展或修改该行为,但不应该改变父类的行为。
- 接口隔离原则(Interface Segregation Principle - ISP):
接口隔离原则要求一个类不应该强迫其客户端依赖于它们不需要的接口。应该根据客户端的需求定义小而精确的接口,而不是大而笨重的接口。
- 依赖倒置原则(Dependency Inversion Principle - DIP):
依赖倒置原则要求高级模块不应该依赖于低级模块,它们都应该依赖于抽象。具体来说,这意味着应该通过抽象接口或抽象类来定义模块之间的依赖关系,而不是直接依赖于具体实现。
- 迪米特法则(Law of Demeter - LoD):
如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。
- 合成复用原则(Composite Reuse Principle - CRP):
合成复用原则鼓励通过组合(合成)已有的类来实现新功能,而不是通过继承现有的类。这样可以降低系统的耦合度,并且更加灵活。
源码位置
全部源码github.com/xs-alpha/designPattern
参考文献
- 《大话设计模式》
-
慕课视频《Java设计模式精讲 Debug方式+内存分析》
如有内容侵权,麻烦联系博主删除