Java设计模式之备忘录模式(Memento)

备忘录模式用于在不破坏封装性的情况下,捕获对象的内部状态,以便在需要时恢复。文章详细介绍了备忘录模式的定义、基本结构、白箱和黑箱备忘录的区别及实现,以及模式的应用场景。通过举例游戏存档,解释了备忘录模式如何保存和恢复对象状态。
摘要由CSDN通过智能技术生成

一、概述

当我们打游戏时,如果打boss时挂了,我们希望下次再打boss时,玩家的战斗值能恢复到打boss之前。就是说,我们希望在某一时刻,可以将游戏的各种数据保存,这样当我们需要时,可以恢复到这个状态。这里引入了一种新的模式——备忘录模式。

二、备忘录模式

1. 定义

备忘录模式,在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。(引自《大化设计模式》)

2. 基本结构图

Originator:发起人。Originator负责创建一个备忘录Memento,用来记录当前时刻它的内部状态,并可以使用备忘录恢复内部状态。Originator根据需要决定Memento存储Originator的哪些内部状态,即哪些属性需要保存就将哪些属性存储在Memento中。

Memento:备忘录。负责存储Originator对象的内部状态,并防止Originator以外的其他对象访问备忘录。备忘录有两个接口,Caretaker只能看到备忘录的窄接口,它只能将备忘录传递给其他对象。Originator能够看到一个宽接口,允许访问它返回到先前状态所需的所有数据。

Caretaker:负责人(或称管理者)。负责保存好备忘录,不能对备忘录的内容进行操作或检查。

在备忘录的角色中,定义了它必须对不同的人提供不同的接口,对发起人提供宽接口,对其它人提供窄接口。

什么意思呢?发起人可以查看备忘录的属性,并对备忘录中的属性进行操作(这是必然的,备忘录中存储的就是发起人需要保存的属性);而负责人不可以查看或操作备忘录中的属性。

由于这个区别,备忘录可以有两种实现:

一是只提供宽接口,这种实现方式我们称之为白箱备忘录。

一是提供双接口,宽接口+窄接口,这种实现方式我们称之为黑箱备忘录。

三、白箱备忘录

1. 定义

上面已经说过,在备忘录的角色中,定义了它必须对不同的人提供不同的接口,对发起人提供宽接口,对其它人提供窄接口。如果都提供宽接口,我们称之为白箱备忘录。

2. 结构图

 类图和上面的基本类图一致,角色都是这些,具体角色这里就不再赘述。

3. 基本代码

直接上代码进行说明。

备忘录Memento

public class Memento {
	//需要保存的数据属性,可以有多个
	private String state;

	//构造函数,将相关数据导入
	public Memento(String state) {
		this.state = state;
	}
	
	public String getState() {
		return state;
	}
}

发起人Originator

public class Originator {
	//Originator的内部状态,即需要保存的属性,可以有多个
	private String state;

	public String getState() {
		return state;
	}

	public void setState(String state) {
		this.state = state;
	}
	
	//创建一个备忘录,将当前需要保存的信息导入并实例化出一个Memento对象
	//即Originator需要保存的信息state存储在Memento对象中
	public Memento createMemento() {
		return new Memento(this.state);
	}
	
	//恢复备忘录,将Memento导入,并将相关数据恢复
	public void restoreMemento(Memento memento) {
		this.state = memento.getState();
	}
}

负责人Caretaker

public class Caretaker {
	private Memento memento;

	public Memento getMemento() {
		return memento;
	}

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

测试类

public class MementoTest {

	public static void main(String[] args) {
		Originator o = new Originator();
		o.setState("On");//设置Originator的初始状态属性值为"On"
		System.out.println("初始状态为:"+o.getState());//显示初始状态
		
		//保存原始状态信息,由于封装在了Memento中,我们并不知道具体都保存了哪些属性
		Caretaker c = new Caretaker();
		c.setMemento(o.createMemento());
		
		//改变Originator的状态属性值为"Off"
		o.setState("Off");
		System.out.println("更改状态为:"+o.getState());//显示更改后的状态
		
		//恢复初始状态
		o.restoreMemento(c.getMemento());
		System.out.println("恢复初始状态为:"+o.getState());
	}
}

 结果

4. 说明

  • 优点:实现简单。
  • 缺点:封装设计不好,安全性不够好。

关于封装性不好,我这里说一下,这里的封装性不好是指备忘录Memento的封装性不好,它将它的方法(构造函数和getState())暴露给了发起人Originator以外的人了,安全性不够好。

四、黑箱备忘录

1. 定义

在备忘录的角色中,定义了它必须对不同的人提供不同的接口,对发起人提供宽接口,对其它人提供窄接口。
如果宽窄接口同时提供,我们称之为黑箱备忘录。

那么,如何实现宽窄接口呢,内部类就是一个很好的方法。我们可以把备忘录类设计成发起人类的内部类,但这样还有一个问题就是同一包中的其它类也能访问到这个内部类,为了解决这个问题,可以将备忘录这个内部类的方法设成私有的,这样既可以保证很好的封装性,也可以保证发起人可以访问到。

2. 结构图

3. 基本代码

直接上代码~~

IMemento接口

public interface IMemento {
	//提供一个窄接口方法,根据需要编写,可以没有方法,这里设置这个方法只是为了区分宽窄接口方法
	public void narrowMethod();
}

发起人Originator

public class Originator {
	private String state;

	public String getState() {
		return state;
	}

	public void setState(String state) {
		this.state = state;
	}
	
	//创建一个备忘录,将当前需要保存的信息导入并实例化出一个Memento对象
	//即Originator需要保存的信息state存储在Memento对象中
	public IMemento createMemento() {
		return new Memento(this.state);
	}
	
	//存储备忘录,将Memento导入,并将相关数据恢复
	public void restoreMemento(IMemento imemento) {
		Memento memento = (Memento)imemento;
		this.state = memento.getState();
	}
	
	//将备忘录设成内部私有类,不允许外部访问
	private class Memento implements IMemento{
		private String state;
		
		//无参构造函数
		private Memento() {
			super();
		}
		
		//有参构造函数,保存发起人需要保存的状态信息
		private Memento(String state) {
			this.state = state;
		}

		private String getState() {
			return state;
		}

		private void setState(String state) {
			this.state = state;
		}

		@Override
		public void narrowMethod() {
			System.out.println("这是窄接口方法!");
		}
	}

	//提供了一个可以获取窄接口的方法
	public IMemento getiMemento() {
		return new Memento();
	}
}

负责人Caretaker

public class Caretaker {
	//这里就只能看到IMemento接口,连Memento里面的属性都看不到了
	private IMemento iMemento;

	public IMemento getiMemento() {
		return iMemento;
	}

	public void setiMemento(IMemento iMemento) {
		this.iMemento = iMemento;
	}
}

测试类

public class Test {

	public static void main(String[] args) {
		//使用宽接口
		Originator o = new Originator();
		o.setState("On");
		System.out.println("初始状态为:"+o.getState());
		
		Caretaker c = new Caretaker();
		c.setiMemento(o.createMemento());
		
		o.setState("Off");
		System.out.println("更改状态为:"+o.getState());
		
		o.restoreMemento(c.getiMemento());
		System.out.println("恢复初始状态为:"+o.getState());
		
		//使用窄接口
		IMemento memento = o.getiMemento();
		memento.narrowMethod();
	}
}

结果与上面一致

五、总结

1. 关于这两个例子

  • 这里的白箱和黑箱都是记录了单个状态(单check点)的信息,也可以实现多个check点的信息备忘,很容易,可以多个String属性,或者换成List,或者Map,或者Bean,然后添加、获得,都ok。
  • 可以进行模式的改进,主要就是将上面角色的功能改改,比如可以扩展负责人的功能,让负责人的功能更强大,从而让客户端的操作更少,解放客户端。或者进一步强大发起人的功能,都可以。

2. 关于各角色的作用

这里再次总结一下:

(1)发起人Originator

  • 创建了一个含有当前内部状态的备忘录对象。
  • 使用备忘录对象存储其内部需要存储的状态。

(2)备忘录Memento

  • 将发起人对象的内部状态存储起来,备忘录能够根据发起人对象的判断来决定存储多少发起人对象的内部状态。
  • 备忘录能保护其内容不被发起人对象之外的所有对象所读取。

(3)负责人Caretaker

  • 负责保存备忘录对象。
  • 不检查备忘录对象的内容。

3. 关于备忘录模式

使用备忘录模式来实现保存对象的历史状态可以很有效地保持封装边界。使用备忘录可以有效地避免暴露那些需要Originator管理但又必须存储Originator之外的一些属性,并且只对Originator开放,从而保持了封装边界。但如果Originator中需要保存的属性过多,或者需要进行频繁的创建、恢复操作,则会造成很大的开销,这就得不偿失。

4. 应用

关于备忘录模式应用最广泛当属自述历史模式了。这里简单介绍下,就是将发起人、负责人写在一个类中,也就是说发起人还兼顾起到负责人的作用。简单写下代码吧。

IMemento接口

public interface IMemento {
	
}

这里什么方法都没写,其实IMemento接口就只是起到了一个作用,使外部对象不能访问备忘录的内部信息。

负责人Caretaker,和上面的相同

public class Caretaker {
	//这里就只能看到IMemento接口,连Memento里面的属性都看不到了
	private IMemento iMemento;

	public IMemento getiMemento() {
		return iMemento;
	}

	public void setiMemento(IMemento iMemento) {
		this.iMemento = iMemento;
	}
}

发起人Originator

public class Originator {
	//需要保存的状态
	private String state;
	//维护一个负责人角色的对象
	private Caretaker caretaker = new Caretaker();

	public String getState() {
		return state;
	}

	public void setState(String state) {
		this.state = state;
	}
	
	//创建一个备忘录,并通过自身维护的负责人角色进行存储,保存了需要保存的信息state属性值
	public void createMemento() {
		caretaker.setiMemento(new Memento(this.state));
	}
	
	//存储备忘录,这里略有不同,通过自身维护的负责人角色获取到备忘录信息
	//恢复状态时调用
	public void restoreMemento() {
		Memento memento = (Memento) caretaker.getiMemento();
		this.state = memento.getState();
	}
	
	//将备忘录设成内部私有类,不允许外部访问
	private class Memento implements IMemento{
		private String state;
		
		//有参构造函数,保存发起人需要保存的状态信息
		private Memento(String state) {
			this.state = state;
		}

		private String getState() {
			return state;
		}
	}

}

测试类

public class Test {

	public static void main(String[] args) {
		Originator o = new Originator();
		o.setState("初始状态");
		System.out.println(o.getState());
		//创建备忘录,存储state
		o.createMemento();
		//修改state
		o.setState("修改状态值");
		System.out.println(o.getState());
		//恢复初始状态
		o.restoreMemento();
		System.out.println(o.getState());
	}

}

结果:

从测试类可以看出,客户端现在只能发现Originator这一个对象,其他一无所知,这样其实封装性更好,也使得客户端的代码更简单。

好了,就介绍这么多。累shi~~ 

 

写在最后,

本文主要是小猫看了《大话设计模式》的一些记录笔记,再加之自己的一些理解整理出此文,方便以后查阅,仅供参考。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值