備忘錄模式(Memento Pattern)又稱爲快照模式(Snapshot Pattern)或者令牌模式(Token Pattern),是指在不破壞封裝的前提下,捕獲一個內部狀態,並在對象以外保存這個狀態。這樣之後就可將該對象恢復到原先保存的狀態。web
在軟件系統中,備忘錄模式爲咱們提供了一種「後悔藥」的機制,它經過存儲系統各個歷史狀態的快照,使得咱們能夠在任意時刻將系統回滾到某一個歷史狀態。spring
1、備忘錄模式的應用場景
咱們機會每天都在使用備忘錄模式,好比使用Git、SVN提供一種代碼版本撤回的功能。還有遊戲的存檔功能,經過將遊戲當前進度存儲到本地文件系統或數據庫中,使得下次繼續遊戲時,玩家能夠從以前的位置繼續進行。數據庫
備忘錄模式適用於如下兩個場景:app
- 須要保存歷史快照的場景;
- 但願在對象以外保存狀態,且除了本身其它類對象沒法訪問狀態保存具體內容。
備忘錄模式主要包含三種角色:框架
- 發起人角色(Orgainator):負責建立一個備忘錄,記錄自身須要保存的狀態,具有狀態回滾功能;
- 備忘錄角色(Memento):用於存儲發起人的內部狀態,且能夠防止發起人之外的對象進行訪問;
- 備忘錄管理員(Caretaker):負責存儲,提供管理備忘錄,沒法對備忘錄內容進行操做和訪問。
1.1 利用壓棧管理落地備忘錄模式
咱們在網頁上寫文章或者博客都使用過富文本編輯器,它會附帶草稿箱、撤銷等這樣的功能。編輯器
下面使用代碼來實現這樣的功能。假設咱們須要發佈一篇文章,這篇文章的編輯過程須要花很長的時間,編輯的過程當中會不停的撤銷,保存草稿、修改。首先建立發起人角色編輯器 Editor 類:ide
public class Editor { private String title; private String content; private String imgs; public Editor(String title, String content, String imgs) { this.title = title; this.content = content; this.imgs = imgs; } public ArticleMemento save2Memento() { ArticleMemento articleMemento = new ArticleMemento(this.title, this.content, this.imgs); return articleMemento; } public void undoFromMemento(ArticleMemento articleMemento) { this.title = articleMemento.getTitle(); this.content = articleMemento.getContent(); this.imgs = articleMemento.getImgs(); } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public String getImgs() { return imgs; } public void setImgs(String imgs) { this.imgs = imgs; } @Override public String toString() { return "Editor{" + "title='" + title + '\'' + ", content='" + content + '\'' + ", imgs='" + imgs + '\'' + '}'; } }
而後建立備忘錄角色 ArticleMemento 類:this
public class ArticleMemento { private String title; private String content; private String imgs; public ArticleMemento(String title, String content, String imgs) { this.title = title; this.content = content; this.imgs = imgs; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public String getImgs() { return imgs; } public void setImgs(String imgs) { this.imgs = imgs; } @Override public String toString() { return "ArticleMemento{" + "title='" + title + '\'' + ", content='" + content + '\'' + ", imgs='" + imgs + '\'' + '}'; } }
建立備忘錄管理角色草稿箱 DraftBox 類:debug
public class DraftBox { private final Stack<ArticleMemento> STACK = new Stack<>(); public ArticleMemento getMemento() { ArticleMemento articleMemento = STACK.pop(); return articleMemento; } public void addMemento(ArticleMemento articleMemento) { STACK.push(articleMemento); } }
草稿箱的Stack類是Vector的一個子類,它實現了一個標準的後進先出的棧。rest
2、備忘錄模式在源碼中的體現
備忘錄模式在框架源碼中的應用仍是比較少見的,主要仍是結合具體的應用場景來使用。spring中的webfolw源碼StateManageableMessageContext接口,咱們來看它的源碼:
public interface StateManageableMessageContext extends MessageContext { /** * Create a serializable memento, or token representing a snapshot of the internal state of this message context. * @return the messages memento */ public Serializable createMessagesMemento(); /** * Set the state of this context from the memento provided. After this call, the messages in this context will match * what is encapsulated inside the memento. Any previous state will be overridden. * @param messagesMemento the messages memento */ public void restoreMessages(Serializable messagesMemento); /** * Configure the message source used to resolve messages added to this context. May be set at any time to change how * coded messages are resolved. * @param messageSource the message source * @see MessageContext#addMessage(MessageResolver) */ public void setMessageSource(MessageSource messageSource); }
createMessagesMemento()建立一個消息備忘錄。能夠看一下實現類:
public class DefaultMessageContext implements StateManageableMessageContext { private static final Log logger = LogFactory.getLog(DefaultMessageContext.class); private MessageSource messageSource; @SuppressWarnings("serial") private Map<Object, List<Message>> sourceMessages = new AbstractCachingMapDecorator<Object, List<Message>>( new LinkedHashMap<Object, List<Message>>()) { protected List<Message> create(Object source) { return new ArrayList<Message>(); } }; /** * Creates a new default message context. Defaults to a message source that simply resolves default text and cannot * resolve localized message codes. */ public DefaultMessageContext() { init(null); } /** * Creates a new default message context. * @param messageSource the message source to resolve messages added to this context */ public DefaultMessageContext(MessageSource messageSource) { init(messageSource); } public MessageSource getMessageSource() { return messageSource; } // implementing message context public Message[] getAllMessages() { List<Message> messages = new ArrayList<Message>(); for (List<Message> list : sourceMessages.values()) { messages.addAll(list); } return messages.toArray(new Message[messages.size()]); } public Message[] getMessagesBySource(Object source) { List<Message> messages = sourceMessages.get(source); return messages.toArray(new Message[messages.size()]); } public Message[] getMessagesByCriteria(MessageCriteria criteria) { List<Message> messages = new ArrayList<Message>(); for (List<Message> sourceMessages : this.sourceMessages.values()) { for (Message message : sourceMessages) { if (criteria.test(message)) { messages.add(message); } } } return messages.toArray(new Message[messages.size()]); } public boolean hasErrorMessages() { for (List<Message> sourceMessages : this.sourceMessages.values()) { for (Message message : sourceMessages) { if (message.getSeverity() == Severity.ERROR) { return true; } } } return false; } public void addMessage(MessageResolver messageResolver) { Locale currentLocale = LocaleContextHolder.getLocale(); if (logger.isDebugEnabled()) { logger.debug("Resolving message using " + messageResolver); } Message message = messageResolver.resolveMessage(messageSource, currentLocale); List<Message> messages = sourceMessages.get(message.getSource()); if (logger.isDebugEnabled()) { logger.debug("Adding resolved message " + message); } messages.add(message); } public void clearMessages() { sourceMessages.clear(); } // implementing state manageable message context public Serializable createMessagesMemento() { return new LinkedHashMap<Object, List<Message>>(sourceMessages); } @SuppressWarnings("unchecked") public void restoreMessages(Serializable messagesMemento) { sourceMessages.putAll((Map<Object, List<Message>>) messagesMemento); } public void setMessageSource(MessageSource messageSource) { if (messageSource == null) { messageSource = new DefaultTextFallbackMessageSource(); } this.messageSource = messageSource; } // internal helpers private void init(MessageSource messageSource) { setMessageSource(messageSource); // create the 'null' source message list eagerly to ensure global messages are indexed first this.sourceMessages.get(null); } public String toString() { return new ToStringCreator(this).append("sourceMessages", sourceMessages).toString(); } private static class DefaultTextFallbackMessageSource extends AbstractMessageSource { protected MessageFormat resolveCode(String code, Locale locale) { return null; } } }
主要邏輯就至關因而給Message留一個備份,以備恢復之用。
3、備忘錄模式的優缺點
優勢:
- 簡化發起人職責,隔離狀態存儲與獲取,實現了信息的封裝,客戶端無需關心狀態的保存細節;
- 提供狀態回滾功能。
缺點: 消耗資源:若是須要保存的狀態過多時,每一次保存都會消耗不少內存。