「补课」进行时:设计模式系列java
相信每一个程序猿,天天工做都会使用版本控制工具,不论是微软提供的 vss 仍是 tfs ,又或者是开源的 svn 或者 git ,天天下班前,总归会使用版本控制工具提交一版代码。git
版本管理工具是让咱们在代码出问题的时候,能够方便的获取到以前的版本进行版本回退,尤为是在项目发布投运的时候,当出现问题的时候直接获取上一个版本进行回滚操做。设计模式
在这个操做中间,最重要的就是保存以前的状态,那么如何保存以前的状态?安全
操做很简单,咱们能够定义一个中间变量,保留这个原始状态。ide
先定义一个版本管理 Git 类:svn
public class Git { private String state; // 版本发生改变,如今是 version2 public void changeState() { this.state = "version2"; } public String getState() { return state; } public void setState(String state) { this.state = state; } }
而后是一个场景 Client 类:函数
public class Client { public static void main(String[] args) { Git git = new Git(); // 初始化版本 git.setState("version1"); System.out.println("当前的版本信息:"); System.out.println(git.getState()); // 记录下当前的状态 Git backup = new Git(); backup.setState(git.getState()); // 提交一个版本,版本进行改变 git.changeState(); System.out.println("提交一个版本后的版本信息:"); System.out.println(git.getState()); // 回退一个版本,版本信息回滚 git.setState(backup.getState()); System.out.println("回退一个版本后的版本信息:"); System.out.println(git.getState()); } }
执行结果:工具
当前的版本信息: version1 提交一个版本后的版本信息: version2 回退一个版本后的版本信息: version1
程序运行正确,输出结果也是咱们指望的,可是结果正确并不表示程序是合适的。this
在场景类 Client 类中,这个是高层模块,如今却在高层模块中作了中间临时变量 backup 的状态的保持,为何一个状态的保存和恢复要让高层模块来负责呢?设计
这个中间临时变量 backup 应该是 Git 类的职责,而不是让一个高层次的模块来进行定义。
咱们新建一个 Memento 类,用做负责状态的保存和备份。
public class Memento { private String state; public Memento(String state) { this.state = state; } public String getState() { return state; } public void setState(String state) { this.state = state; } }
新建一个 Memento ,用构造函数来传递状态 state ,修改上面的 Git 类,新增两个方法 createMemento()
和 restoreMemento()
,用来建立备忘录以及恢复一个备忘录。
public class Git { private String state; // 版本发生改变,如今是 version2 public void changeState() { this.state = "version2"; } public String getState() { return state; } public void setState(String state) { this.state = state; } // 建立一个备忘录 public Memento createMemento(String state) { return new Memento(state); } // 恢复一个备忘录 public void restoreMemento(Memento memento) { this.setState(memento.getState()); } }
修改后的场景类:
public class Client { public static void main(String[] args) { Git git = new Git(); // 初始化版本 git.setState("version1"); System.out.println("当前的版本信息:"); System.out.println(git.getState()); // 记录下当前的状态 Memento mem = git.createMemento(git.getState()); // 提交一个版本,版本进行改变 git.changeState(); System.out.println("提交一个版本后的版本信息:"); System.out.println(git.getState()); // 项目发布失败,回滚状态 git.restoreMemento(mem); System.out.println("回退一个版本后的版本信息:"); System.out.println(git.getState()); } }
运行结果和以前的案例保持一致,那么这就结束了么,固然没有,虽然咱们在 Client 中再也不须要重复定义 Git 类了,可是这是对迪米特法则的一个亵渎,它告诉咱们只和朋友类通讯,那这个备忘录对象是咱们必需要通讯的朋友类吗?对高层模块来讲,它最但愿要作的就是建立一个备份点,而后在须要的时候再恢复到这个备份点就成了,它不用关心到底有没有备忘录这个类。
那咱们能够对这个备忘录的类再作一下包装,建立一个管理类,专门用做管理这个备忘录:
public class Caretaker { private Memento memento; public Memento getMemento() { return memento; } public void setMemento(Memento memento) { this.memento = memento; } }
很是简单纯粹的一个 JavaBean ,甭管它多简单,只要有用就成,咱们来看场景类如何调用:
public class Client { public static void main(String[] args) { Git git = new Git(); // 建立一个备忘录管理者 Caretaker caretaker = new Caretaker(); // 初始化版本 git.setState("version1"); System.out.println("当前的版本信息:"); System.out.println(git.getState()); // 记录下当前的状态 caretaker.setMemento(git.createMemento(git.getState())); // 提交一个版本,版本进行改变 git.changeState(); System.out.println("提交一个版本后的版本信息:"); System.out.println(git.getState()); // 项目发布失败,回滚状态 git.restoreMemento(caretaker.getMemento()); System.out.println("回退一个版本后的版本信息:"); System.out.println(git.getState()); } }
如今这个备份者就相似于一个备份的仓库管理员,建立一个丢进去,须要的时候再拿出来。这就是备忘录模式。
备忘录模式(Memento Pattern)提供了一种弥补真实世界缺陷的方法,让“后悔药”在程序的世界中真实可行,其定义以下:
Without violating encapsulation,capture and externalize an object's internalstate so that the object can be restored to this state later.(在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象以外保存这个状态。这样之后就可将该对象恢复到原先保存的状态。)
发起人:
public class Originator { private String state; public String getState() { return state; } public void setState(String state) { this.state = state; } // 建立一个备忘录 public Memento createMemento() { return new Memento(this.state); } // 恢复一个备忘录 public void restoreMemento(Memento memento) { this.setState(memento.getState()); } }
备忘录:
public class Memento { private String state; public Memento(String state) { this.state = state; } public String getState() { return state; } public void setState(String state) { this.state = state; } }
备忘录管理员:
public class Caretaker { // 备忘录对象 private Memento memento; public Memento getMemento() { return memento; } public void setMemento(Memento memento) { this.memento = memento; } }
场景类:
public class Client { public static void main(String[] args) { // 定义发起人 Originator originator = new Originator(); // 定义备忘录管理员 Caretaker caretaker = new Caretaker(); // 建立一个备忘录 caretaker.setMemento(originator.createMemento()); // 恢复一个备忘录 originator.restoreMemento(caretaker.getMemento()); } }
咱们能够经过复制的方式产生一个对象的内部状态,这是一个很好的办法,发起人角色只要实现 Cloneable 就成,比较简单:
public class Originator implements Cloneable { // 内部状态 private String state; public String getState() { return state; } public void setState(String state) { this.state = state; } private Originator backup; // 建立一个备忘录 public void createMemento() { this.backup = this.clone(); } // 恢复一个备忘录 public void restoreMemento() { this.setState(this.backup.getState()); } // 克隆当前对象 @Override protected Originator clone() { try { return (Originator) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } }
备忘录管理员:
public class Caretaker { // 发起人对象 private Originator originator; public Originator getOriginator() { return originator; } public void setOriginator(Originator originator) { this.originator = originator; } }
场景类:
public class Client { public static void main(String[] args) { // 定义发起人 Originator originator = new Originator(); // 建立初始状态 originator.setState("初始状态"); System.out.println("初始状态:" + originator.getState()); // 建立备份 originator.createMemento(); // 修改状态 originator.setState("修改后的状态"); System.out.println("修改后的状态:" + originator.getState()); // 恢复状态 originator.restoreMemento(); System.out.println("恢复后的状态:" + originator.getState()); } }
运行结果是咱们所但愿的,程序精简了不少,并且高层模块的依赖也减小了,这正是咱们指望的效果。
可是咱们来考虑一下原型模式深拷贝和浅拷贝的问题,在复杂的场景下它会让咱们的程序逻辑异常混乱,出现错误也很难跟踪。所以 Clone 方式的备忘录模式适用于较简单的场景。
咱们天天使用的 Windows 是能够拥有多个备份时间点的,系统出现问题,咱们能够自由选择须要恢复的还原点。
咱们上面的备忘录模式尚且不具备这个功能,只能有一个备份,想要有多个备份也比较简单,咱们在备份的时候作一个标记,简单一点可使用一个字符串。
咱们只要把通用代码中的 Caretaker 管理员稍作修改就能够了:
public class Caretaker { // 容纳备忘录的容器 private Map<String, Memento> mementoMap = new HashMap<>(); public Memento getMemento(String keys) { return mementoMap.get(keys); } public void setMemento(String key, Memento memento) { this.mementoMap.put(key, memento); } }
对场景类作部分修改:
public class Client { public static void main(String[] args) { // 定义发起人 Originator originator = new Originator(); // 定义备忘录管理员 Caretaker caretaker = new Caretaker(); // 建立两个备忘录 caretaker.setMemento("001", originator.createMemento()); caretaker.setMemento("002", originator.createMemento()); // 恢复一个指定的备忘录 originator.restoreMemento(caretaker.getMemento("002")); } }
在系统管理上,一个备份的数据是彻底、绝对不能修改的,它保证数据的洁净,避免数据污染而使备份失去意义。
在咱们的程序中也有着一样的问题,备份是不能被褚篡改的,那么也就是须要缩小备忘录的访问权限,保证只有发起人可读就能够了。
这个很简单,直接使用内置类就能够了:
public class Originator { private String state; public String getState() { return state; } public void setState(String state) { this.state = state; } // 建立一个备忘录 public IMemento createMemento() { return new Memento(this.state); } // 恢复一个备忘录 public void restoreMemento(IMemento memento) { this.setState(((Memento)memento).getState()); } private class Memento implements IMemento { private String state; private Memento(String state) { this.state = state; } public String getState() { return state; } public void setState(String state) { this.state = state; } } }
这里使用了一个 IMemento
接口,这个接口其实是一个空接口:
public interface IMemento { }
这个空接口的做用是用做公共的访问权限。
下面看一下备忘录管理者的变化:
public class Caretaker { // 备忘录对象 private IMemento memento; public IMemento getMemento() { return memento; } public void setMemento(IMemento memento) { this.memento = memento; } }
上面这段示例所有经过接口访问,若是咱们想访问它的属性貌似是没法访问到了。
可是安全是相对的,没有绝对的安全,咱们可使用 refelect 反射修改 Memento 的数据。
在这里咱们使用了一个新的设计方法:双接口设计,咱们的一个类能够实现多个接口,在系统设计时,若是考虑对象的安全问题,则能够提供两个接口,一个是业务的正常接口,实现必要的业务逻辑,叫作宽接口;另一个接口是一个空接口,什么方法都没有,其目的是提供给子系统外的模块访问,好比容器对象,这个叫作窄接口,因为窄接口中没有提供任何操纵数据的方法,所以相对来讲比较安全。