「補課」進行時:設計模式系列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 的數據。
在這裏咱們使用了一個新的設計方法:雙接口設計,咱們的一個類能夠實現多個接口,在系統設計時,若是考慮對象的安全問題,則能夠提供兩個接口,一個是業務的正常接口,實現必要的業務邏輯,叫作寬接口;另一個接口是一個空接口,什麼方法都沒有,其目的是提供給子系統外的模塊訪問,好比容器對象,這個叫作窄接口,因爲窄接口中沒有提供任何操縱數據的方法,所以相對來講比較安全。