我用備忘錄模式設計了簡易的版本控制系統
“Ctrl+Z”是什麼操作?各位都用過,並且經常使用吧?撤銷!撤銷上一個操作返回上一個狀態,甚至撤銷好幾個操作,返回到幾個操作之前的狀態。這個操作非常有用,一旦我們某一步操作失誤,可以選擇撤銷操作來返回原來的無錯狀態。
那麼系統怎麼知道每一步的狀態呢?它一定儲存了一定數量的歷史狀態!就像Git版本控制一樣,儲存著每一次提交的狀態,使用者可以隨時reset到歷史某個狀態,就像一個備忘錄一樣,儲存了某些階段的狀態。
1.備忘錄模式簡介
類似於上述引言的例子,在軟體系統的操作過程中,難免會出現一些不當的操作,使得系統狀態出現某些故障。如果能夠有一種機制——能夠儲存系統每個階段的狀態,當使用者操作失誤的時候,可以撤銷不當的操作,回到歷史某個階段——那麼軟體系統將更加靈活和人性化。
有沒有這樣的一種解決方案呢?有!那就是備忘錄模式。備忘錄模式提供了一種狀態恢復的機制,使用者可以方便地回到指定的某個歷史狀態。很多軟體的撤銷操作,就使用了備忘錄模式。
備忘錄模式:
在不破壞封裝的前提下捕獲一個物件的內部狀態,並在該物件之外儲存這個狀態,這樣可以在以後將物件恢復到原先儲存的狀態。
2.備忘錄模式結構
備忘錄模式的UML圖如下所示:
備忘錄模式主要有以下角色:
- Originator(原發器):通過建立一個備忘錄類儲存當前的內部狀態,也可以使用備忘錄來恢復其內部狀態,通常是將系統中需要儲存內部狀態的類設計為原發器;
- Memento(備忘錄):用於儲存原發器的內部狀態。備忘錄的設計可以參考原發器的設計,根據需要確定備忘錄類中的屬性;除了原發器類物件,不允許其他物件修改備忘錄。
- Caretaker(負責人):負責儲存備忘錄,可以儲存一個或多個備忘錄物件,但是負責人只負責儲存物件,不能修改物件,也不必知道物件的實現細節。(看好了,負責人可以儲存多個備忘錄物件,想一想這有什麼用?是不是可以儲存多個歷史狀態?實現多步撤銷操作了)
備忘錄模式的關鍵是備忘錄類和負責人類的設計,以下是上述三個角色的典型實現:
#ifndef __DEMO_H__
#define __DEMO_H__
// 前向宣告
class Memento;
// 原發器 典型實現
class Originator
{
public:
Originator(){
state = "";
}
Originator(String iState){
state = iState;
}
// 建立備忘錄物件
Memento* createMemento(){
return new Memento(this);
}
// 利用備忘錄物件恢復原發器狀態
void restoreMemento(Memento* m){
state = m->getState();
}
void setState(string iState){
state = iState;
}
string getState(){
return state;
}
private:
string state;
};
// 備忘錄 典型實現(仿照原生器的設計)
class Memento
{
public:
Memento(){
state = "";
}
Memento(Originator* o){
state = o->getState();
}
void setState(String iState){
state = iState;
}
string getState(){
return state;
}
private:
String state;
};
// 負責人 典型實現
class Caretaker
{
public:
Caretaker(){}
Memento* getMemento(){
return memento;
}
void setMemento(Memento *m){
memento = m;
}
private:
Memento* memento;
};
// 客戶端 示例程式碼
int main()
{
// 建立原發器物件
Originator o = new Originator("狀態1");
// 建立負責人物件
Caretaker *c = new Caretaker();
c->setMemento(o->createMemento());
o->setState("狀態2");
// 從負責人物件中取出備忘錄物件,實現撤銷
o->restoreMemento(c->getMemento());
return 0;
}
#endif
3.備忘錄模式程式碼例項
Jungle正在為程式碼版本管理苦惱,有時候為了嘗試某個功能就去修改程式碼,導致原有的健壯的程式碼被破壞。所以Jungle希望能夠設計一個程式碼儲存和版本回退功能的demo,方便程式碼的管理。
本例項中,原生器為CodeVersion,具有版本號version、提交日期date和標籤label三個狀態需要備忘錄Memento儲存;管理者是CodeManager,具有提交程式碼commit(即儲存一個版本)、回退到指定版本switchToPointedVersion(即撤銷操作)和檢視提交歷史codeLog的功能。該例項的UML圖如下圖,具體程式碼如下。(完整程式碼資源見https://github.com/FengJungle/DesignPattern)
3.1.備忘錄Memento
#ifndef __MEMENTO_H__
#define __MEMENTO_H__
class Memento
{
public:
Memento(){}
Memento(int iVersion, string iDate, string iLabel){
version = iVersion;
date = iDate;
label = iLabel;
}
void setVersion(int iVersion){
version = iVersion;
}
int getVersion(){
return version;
}
void setLabel(string iLabel){
label = iLabel;
}
string getLabel(){
return label;
}
void setDate(string iDate){
date = iDate;
}
string getDate(){
return date;
}
private:
int version;
string date;
string label;
};
#endif
3.2.原生器CodeVersion
#ifndef __CODEVERSION_H__
#define __CODEVERSION_H__
#include <iostream>
using namespace std;
#include "Memento.h"
// 原生器:CodeVersion
class CodeVersion
{
public:
CodeVersion(){
version = 0;
date = "1900-01-01";
label = "none";
}
CodeVersion(int iVersion, string iDate, string iLabel){
version = iVersion;
date = iDate;
label = iLabel;
}
// 儲存程式碼
Memento* save(){
return new Memento(this->version, this->date, this->label);
}
// 回退版本
void restore(Memento* memento){
setVersion(memento->getVersion());
setDate(memento->getDate());
setLabel(memento->getLabel());
}
void setVersion(int iVersion){
version = iVersion;
}
int getVersion(){
return version;
}
void setLabel(string iLabel){
label = iLabel;
}
string getLabel(){
return label;
}
void setDate(string iDate){
date = iDate;
}
string getDate(){
return date;
}
private:
// 程式碼版本
int version;
// 程式碼提交日期
string date;
// 程式碼標籤
string label;
};
#endif
3.3.管理者CodeManager
#ifndef __CODEMANAGER_H__
#define __CODEMANAGER_H__
#include "Memento.h"
#include <vector>
using namespace std;
// 管理者
class CodeManager
{
public:
CodeManager(){}
void commit(Memento* m){
printf("提交:版本-%d, 日期-%s, 標籤-%s\n", m->getVersion(), m->getDate().c_str(), m->getLabel().c_str());
mementoList.push_back(m);
}
// 切換到指定的版本,即回退到指定版本
Memento* switchToPointedVersion(int index){
mementoList.erase(mementoList.begin() + mementoList.size() - index, mementoList.end());
return mementoList[mementoList.size() - 1];
}
// 列印歷史版本
void codeLog(){
for (int i = 0; i < mementoList.size(); i++){
printf("[%d]:版本-%d, 日期-%s, 標籤-%s\n", i, mementoList[i]->getVersion(),
mementoList[i]->getDate().c_str(), mementoList[i]->getLabel().c_str());
}
}
private:
vector<Memento*> mementoList;
};
#endif
3.4.客戶端程式碼示例及效果
#include "Originator.h"
#include "Memento.h"
#include "CodeManager.h"
int main()
{
CodeManager *Jungle = new CodeManager();
CodeVersion* codeVer = new CodeVersion(1001, "2019-11-03", "Initial version");
// 提交初始版本
printf("提交初始版本:\n");
Jungle->commit(codeVer->save());
// 修改一個版本,增加了日誌功能
printf("\n提交一個版本,增加了日誌功能:\n");
codeVer->setVersion(1002);
codeVer->setDate("2019-11-04");
codeVer->setLabel("Add log funciton");
Jungle->commit(codeVer->save());
// 修改一個版本,增加了Qt圖片瀏覽器
printf("\n提交一個版本,增加了Qt圖片瀏覽器:\n");
codeVer->setVersion(1003);
codeVer->setDate("2019-11-05");
codeVer->setLabel("Add Qt Image Browser");
Jungle->commit(codeVer->save());
// 檢視提交歷史
printf("\n檢視提交歷史\n");
Jungle->codeLog();
// 回退到上一個版本
printf("\n回退到上一個版本\n");
codeVer->restore(Jungle->switchToPointedVersion(1));
// 檢視提交歷史
printf("\n檢視提交歷史\n");
Jungle->codeLog();
printf("\n\n");
system("pause");
return 0;
}
程式碼執行結果如下:
這是不是像一個超級簡易版本的程式碼版本控制系統???哈哈哈!
4.總結
優點:
- 實現狀態恢復、撤銷操作的功能,使用者可以恢復到指定的歷史狀態,讓軟體系統更加人性化;
- 備忘錄封裝了資訊,除了原生器以外,其他物件訪問不了備忘錄的程式碼;
缺點:
- 資源消耗大。如果需要儲存原生器物件的多個歷史狀態,那麼將建立多個備忘錄物件;或者如果原生器物件的很多狀態都需要儲存,也將消耗大量儲存資源。
適用環境:
- 儲存一個物件的歷史狀態,系統需要設計回退或者撤銷功能;
- 備忘錄類可以封裝一個物件的歷史狀態,避免物件的歷史狀態被外界修改。
歡迎關注知乎專欄:Jungle是一個用Qt的工業Robot
歡迎關注Jungle的微信公眾號:Jungle筆記
相關文章
- 簡說設計模式——備忘錄模式設計模式
- 極簡設計模式-備忘錄模式設計模式
- 設計模式----備忘錄模式設計模式
- 設計模式 - 備忘錄模式設計模式
- 設計模式 | 備忘錄模式及典型應用設計模式
- 設計模式(十九):備忘錄模式設計模式
- 設計模式之備忘錄模式設計模式
- 我學設計模式 之 備忘模式設計模式
- 設計模式 - 備忘錄模式 ( Memento )設計模式
- GoLang設計模式11 - 備忘錄模式Golang設計模式
- Python設計模式-備忘錄模式Python設計模式
- 大話設計模式—備忘錄模式設計模式
- 設計模式漫談之備忘錄模式設計模式
- java設計模式-備忘錄模式(Memento)Java設計模式
- 23種設計模式之備忘錄模式設計模式
- 備忘錄設計模式知識概括設計模式
- 折騰Java設計模式之備忘錄模式Java設計模式
- 19.java設計模式之備忘錄模式Java設計模式
- 設計模式的征途—20.備忘錄(Memento)模式設計模式
- 抽絲剝繭——備忘錄設計模式設計模式
- iOS設計模式之四:備忘錄模式和命令模式iOS設計模式
- 行為型設計模式 - 備忘錄模式詳解設計模式
- C#設計模式系列:備忘錄模式(Memento)C#設計模式
- 設計模式--備忘錄模式Memento(行為型)設計模式
- JAVA設計模式之 備忘錄模式【Memento Pattern】Java設計模式
- Swift 中的設計模式 #2 觀察者模式與備忘錄模式Swift設計模式
- 備忘錄模式模式
- 深入淺出Java設計之備忘錄模式Java模式
- Rust語言之GoF設計模式:備忘錄Memento模式RustGo設計模式
- php設計模式備忘PHP設計模式
- 《設計模式七》備忘錄、模板方法、狀態模式及設計模式設計總結設計模式
- 無廢話設計模式(16)行為型模式--備忘錄模式設計模式
- 設計模式(Swift) - 2.單例模式、備忘錄模式和策略模式設計模式Swift單例
- 軟體設計模式學習(二十二)備忘錄模式設計模式
- React 全家桶實現一個簡易備忘錄React
- 萌新(我)的Git備忘錄Git
- 設計模式學習筆記(十八)備忘錄模式及其實現設計模式筆記
- Net設計模式例項之備忘錄模式(Memento Pattern)(2)設計模式