作者:小傅哥 博客:https:// - 原創(chuàng)系列專題文章
沉淀、分享、成長,讓自己和他人都能有所收獲!😄
一、前言
實(shí)現(xiàn)不了是研發(fā)的借口?
實(shí)現(xiàn)不了,有時(shí)候是功能復(fù)雜度較高難以實(shí)現(xiàn),有時(shí)候是工期較短實(shí)現(xiàn)不完。而編碼的行為又是一個(gè)不太好量化的過程,同樣一個(gè)功能每個(gè)人的實(shí)現(xiàn)方式不一樣,遇到開發(fā)問題解決問題的速度也不一樣。除此之外還很不好給產(chǎn)品解釋具體為什么要這個(gè)工期時(shí)間,這就像蓋樓的圖紙最終要多少水泥砂漿一樣。那么這時(shí)研發(fā)會(huì)盡可能的去通過一些經(jīng)驗(yàn),制定流程規(guī)范、設(shè)計(jì)、開發(fā)、評審等,確定一個(gè)可以完成的時(shí)間范圍,又避免風(fēng)險(xiǎn)的時(shí)間點(diǎn)后。再被壓縮,往往會(huì)出一些矛盾點(diǎn),能壓縮要解釋為什么之前要那么多時(shí)間,不能壓縮又有各方不斷施加的壓力。因此有時(shí)候不一定是借口,是要考慮如何讓整個(gè)團(tuán)隊(duì)健康的發(fā)展。
鼓勵(lì)有時(shí)比壓力要重要!
在學(xué)習(xí)的過程中,很多時(shí)候我們聽到的都是,你要怎樣,怎樣,你瞧瞧誰誰誰,哪怕今天聽不到這樣的聲音了,但因?yàn)樵?jīng)反復(fù)聽到過而導(dǎo)致內(nèi)心抗拒。雖然也知道自己要去學(xué),但是很難堅(jiān)持,學(xué)著學(xué)著就沒有了方向,看到還有那么多不會(huì)的就更慌了,以至于最后心態(tài)崩了,更不愿意學(xué)。其實(shí)程序員的壓力并不小,想成長幾乎是需要一直的學(xué)習(xí),就像似乎再也不敢說精通java了一樣,知識量實(shí)在是隨著學(xué)習(xí)的深入,越來越深,越來越廣。所以需要,開心學(xué)習(xí),快樂成長!
臨陣的你好像一直很著急!
經(jīng)常的聽到;老師明天就要了你幫我弄弄吧、你給我寫一下完事我就學(xué)這次著急、現(xiàn)在這不是沒時(shí)間學(xué)嗎快給我看看。其實(shí)看到的類似的還有很多,很納悶?zāi)愕闹痹趺磥淼?#xff0c;不太可能,人在家中坐,禍從天上落。老師怎么就那個(gè)時(shí)間找你了,老板怎么就今天管你要了,還不是日積月累你沒有學(xué)習(xí),臨時(shí)抱佛腳亂著急!即使后來真的有人幫你了,但最好不要放松,要盡快學(xué)會(huì),躲得過初一還有初二呢!
二、開發(fā)環(huán)境
JDK 1.8 Idea + Maven 涉及工程一個(gè),可以通過關(guān)注公眾號 :bugstack蟲洞棧 ,回復(fù)源碼下載獲取(打開獲取的鏈接,找到序號18)
工程 描述 itstack-demo-design-17-00 開發(fā)配置文件備忘錄
三、備忘錄模式介紹
備忘錄模式是以可以恢復(fù)或者說回滾,配置、版本、悔棋為核心功能的設(shè)計(jì)模式,而這種設(shè)計(jì)模式屬于行為模式。在功能實(shí)現(xiàn)上是以不破壞原對象為基礎(chǔ)增加備忘錄操作類,記錄原對象的行為從而實(shí)現(xiàn)備忘錄模式。
這個(gè)設(shè)計(jì)在我們平常的生活或者開發(fā)中也是比較常見的,比如:后悔藥、孟婆湯(一下回滾到0),IDEA編輯和撤銷、小霸王游戲機(jī)存檔。當(dāng)然還有我們非常常見的Photoshop,如下;
四、案例場景模擬
在本案例中我們模擬系統(tǒng)在發(fā)布上線的過程中記錄線上配置文件用于緊急回滾
在大型互聯(lián)網(wǎng)公司系統(tǒng)的發(fā)布上線一定是易用、安全、可處理緊急狀況的,同時(shí)為了可以隔離線上和本地環(huán)境,一般會(huì)把配置文件抽取出來放到線上,避免有人誤操作導(dǎo)致本地的配置內(nèi)容發(fā)布出去。同時(shí)線上的配置文件也會(huì)在每次變更的時(shí)候進(jìn)行記錄,包括;版本號、時(shí)間、MD5、內(nèi)容信息和操作人。
在后續(xù)上線時(shí)如果發(fā)現(xiàn)緊急問題,系統(tǒng)就會(huì)需要回滾操作,如果執(zhí)行回滾那么也可以設(shè)置配置文件是否回滾。因?yàn)槊恳粋€(gè)版本的系統(tǒng)可能會(huì)隨著帶著一些配置文件的信息,這個(gè)時(shí)候就可以很方便的讓系統(tǒng)與配置文件一起回滾操作。
我們接下來就使用備忘錄模式,模擬如何記錄配置文件信息。實(shí)際的使用過程中還會(huì)將信息存放到庫中進(jìn)行保存,這里暫時(shí)只是使用內(nèi)存記錄。
五、備忘錄模式記錄配置文件版本信息
備忘錄的設(shè)計(jì)模式實(shí)現(xiàn)方式,重點(diǎn)在于不更改原有類的基礎(chǔ)上,增加備忘錄類存放記錄??赡芷綍r(shí)雖然不一定非得按照這個(gè)設(shè)計(jì)模式的代碼結(jié)構(gòu)來實(shí)現(xiàn)自己的需求,但是對于功能上可能也完成過類似的功能,記錄系統(tǒng)的信息。
除了現(xiàn)在的這個(gè)案例外,還可以是運(yùn)營人員在后臺(tái)erp創(chuàng)建活動(dòng)對信息的記錄,方便運(yùn)營人員可以上下修改自己的版本,而不至于因?yàn)檎`操作而丟失信息。
1. 工程結(jié)構(gòu)
itstack- demo- design- 17 - 00
└── src
├── main
│ └── java
│ └── org. itstack. demo. design
│ ├── Admin. java
│ ├── ConfigFile. java
│ ├── ConfigMemento. java
│ └── ConfigOriginator. java
└── test
└── java
└── org. itstack. demo. design. test
└── ApiTest. java
備忘錄模式模型結(jié)構(gòu)
以上是工程結(jié)構(gòu)的一個(gè)類圖,其實(shí)相對來說并不復(fù)雜,除了原有的配置類(ConfigFile)以外,只新增加了三個(gè)類。 ConfigMemento:備忘錄類,相當(dāng)于是對原有配置類的擴(kuò)展ConfigOriginator:記錄者類,獲取和返回備忘錄類對象信息Admin:管理員類,用于操作記錄備忘信息,比如你一些列的順序執(zhí)行了什么或者某個(gè)版本下的內(nèi)容信息
2. 代碼實(shí)現(xiàn)
2.1 配置信息類
public class ConfigFile {
private String versionNo; // 版本號
private String content; // 內(nèi)容
private Date dateTime; // 時(shí)間
private String operator; // 操作人
// ...get/set
}
配置類可以是任何形式的,這里只是簡單的描述了一個(gè)基本的配置內(nèi)容信息。
2.2 備忘錄類
public class ConfigMemento {
private ConfigFile configFile;
public ConfigMemento ( ConfigFile configFile) {
this . configFile = configFile;
}
public ConfigFile getConfigFile ( ) {
return configFile;
}
public void setConfigFile ( ConfigFile configFile) {
this . configFile = configFile;
}
}
備忘錄是對原有配置類的擴(kuò)展,可以設(shè)置和獲取配置信息。
2.3 記錄者類
public class ConfigOriginator {
private ConfigFile configFile;
public ConfigFile getConfigFile ( ) {
return configFile;
}
public void setConfigFile ( ConfigFile configFile) {
this . configFile = configFile;
}
public ConfigMemento saveMemento ( ) {
return new ConfigMemento ( configFile) ;
}
public void getMemento ( ConfigMemento memento) {
this . configFile = memento. getConfigFile ( ) ;
}
}
記錄者類除了對ConfigFile配置類增加了獲取和設(shè)置方法外,還增加了保存saveMemento()、獲取getMemento(ConfigMemento memento)。 saveMemento:保存?zhèn)渫浀臅r(shí)候會(huì)創(chuàng)建一個(gè)備忘錄信息,并返回回去,交給管理者處理。getMemento:獲取的之后并不是直接返回,而是把備忘錄的信息交給現(xiàn)在的配置文件this.configFile,這部分需要注意。
2.4 管理員類
public class Admin {
private int cursorIdx = 0 ;
private List< ConfigMemento> mementoList = new ArrayList < ConfigMemento> ( ) ;
private Map< String, ConfigMemento> mementoMap = new ConcurrentHashMap < String, ConfigMemento> ( ) ;
public void append ( ConfigMemento memento) {
mementoList. add ( memento) ;
mementoMap. put ( memento. getConfigFile ( ) . getVersionNo ( ) , memento) ;
cursorIdx++ ;
}
public ConfigMemento undo ( ) {
if ( -- cursorIdx <= 0 ) return mementoList. get ( 0 ) ;
return mementoList. get ( cursorIdx) ;
}
public ConfigMemento redo ( ) {
if ( ++ cursorIdx > mementoList. size ( ) ) return mementoList. get ( mementoList. size ( ) - 1 ) ;
return mementoList. get ( cursorIdx) ;
}
public ConfigMemento get ( String versionNo) {
return mementoMap. get ( versionNo) ;
}
}
在這個(gè)類中主要實(shí)現(xiàn)的核心功能就是記錄配置文件信息,也就是備忘錄的效果,之后提供可以回滾和獲取的方法,拿到備忘錄的具體內(nèi)容。 同時(shí)這里設(shè)置了兩個(gè)數(shù)據(jù)結(jié)構(gòu)來存放備忘錄,實(shí)際使用中可以按需設(shè)置。List<ConfigMemento>、Map<String, ConfigMemento>。 最后是提供的備忘錄操作方法;存放(append)、回滾(undo)、返回(redo)、定向獲取(get),這樣四個(gè)操作方法。
3. 測試驗(yàn)證
3.1 編寫測試類
@Test
public void test ( ) {
Admin admin = new Admin ( ) ;
ConfigOriginator configOriginator = new ConfigOriginator ( ) ;
configOriginator. setConfigFile ( new ConfigFile ( "1000001" , "配置內(nèi)容A=哈哈" , new Date ( ) , "小傅哥" ) ) ;
admin. append ( configOriginator. saveMemento ( ) ) ; // 保存配置
configOriginator. setConfigFile ( new ConfigFile ( "1000002" , "配置內(nèi)容A=嘻嘻" , new Date ( ) , "小傅哥" ) ) ;
admin. append ( configOriginator. saveMemento ( ) ) ; // 保存配置
configOriginator. setConfigFile ( new ConfigFile ( "1000003" , "配置內(nèi)容A=么么" , new Date ( ) , "小傅哥" ) ) ;
admin. append ( configOriginator. saveMemento ( ) ) ; // 保存配置
configOriginator. setConfigFile ( new ConfigFile ( "1000004" , "配置內(nèi)容A=嘿嘿" , new Date ( ) , "小傅哥" ) ) ;
admin. append ( configOriginator. saveMemento ( ) ) ; // 保存配置
// 歷史配置(回滾)
configOriginator. getMemento ( admin. undo ( ) ) ;
logger. info ( "歷史配置(回滾)undo:{}" , JSON. toJSONString ( configOriginator. getConfigFile ( ) ) ) ;
// 歷史配置(回滾)
configOriginator. getMemento ( admin. undo ( ) ) ;
logger. info ( "歷史配置(回滾)undo:{}" , JSON. toJSONString ( configOriginator. getConfigFile ( ) ) ) ;
// 歷史配置(前進(jìn))
configOriginator. getMemento ( admin. redo ( ) ) ;
logger. info ( "歷史配置(前進(jìn))redo:{}" , JSON. toJSONString ( configOriginator. getConfigFile ( ) ) ) ;
// 歷史配置(獲取)
configOriginator. getMemento ( admin. get ( "1000002" ) ) ;
logger. info ( "歷史配置(獲取)get:{}" , JSON. toJSONString ( configOriginator. getConfigFile ( ) ) ) ;
}
這個(gè)設(shè)計(jì)模式的學(xué)習(xí)有一部分重點(diǎn)是體現(xiàn)在了單元測試類上,這里包括了四次的信息存儲(chǔ)和備忘錄歷史配置操作。 通過上面添加了四次配置后,下面分別進(jìn)行操作是;回滾1次、再回滾1次,之后向前進(jìn)1次,最后是獲取指定的版本配置。具體的效果可以參考測試結(jié)果。
3.2 測試結(jié)果
23 : 12 : 09.512 [ main] INFO org. itstack. demo. design. test. ApiTest - 歷史配置( 回滾) undo:{ "content" : "配置內(nèi)容A=嘿嘿" , "dateTime" : 159209829432 , "operator" : "小傅哥" , "versionNo" : "1000004" }
23 : 12 : 09.514 [ main] INFO org. itstack. demo. design. test. ApiTest - 歷史配置( 回滾) undo:{ "content" : "配置內(nèi)容A=么么" , "dateTime" : 159209829432 , "operator" : "小傅哥" , "versionNo" : "1000003" }
23 : 12 : 09.514 [ main] INFO org. itstack. demo. design. test. ApiTest - 歷史配置( 前進(jìn)) redo:{ "content" : "配置內(nèi)容A=嘿嘿" , "dateTime" : 159209829432 , "operator" : "小傅哥" , "versionNo" : "1000004" }
23 : 12 : 09.514 [ main] INFO org. itstack. demo. design. test. ApiTest - 歷史配置( 獲取) get:{ "content" : "配置內(nèi)容A=嘻嘻" , "dateTime" : 159320989432 , "operator" : "小傅哥" , "versionNo" : "1000002" }
Process finished with exit code 0
從測試效果上可以看到,歷史配置按照我們的指令進(jìn)行了回滾和前進(jìn),以及最終通過指定的版本進(jìn)行獲取,符合預(yù)期結(jié)果。
六、總結(jié)
此種設(shè)計(jì)模式的方式可以滿足在不破壞原有屬性類的基礎(chǔ)上,擴(kuò)充了備忘錄的功能。雖然和我們平時(shí)使用的思路是一樣的,但在具體實(shí)現(xiàn)上還可以細(xì)細(xì)品味,這樣的方式在一些源碼中也有所體現(xiàn)。 在以上的實(shí)現(xiàn)中我們是將配置模擬存放到內(nèi)存中,如果關(guān)機(jī)了會(huì)導(dǎo)致配置信息丟失,因?yàn)樵谝恍┱鎸?shí)的場景里還是需要存放到數(shù)據(jù)庫中。那么此種存放到內(nèi)存中進(jìn)行回復(fù)的場景也不是沒有,比如;Photoshop、運(yùn)營人員操作ERP配置活動(dòng),那么也就是即時(shí)性的一般不需要存放到庫中進(jìn)行恢復(fù)。另外如果是使用內(nèi)存方式存放備忘錄,需要考慮存儲(chǔ)問題,避免造成內(nèi)存大量消耗。 設(shè)計(jì)模式的學(xué)習(xí)都是為了更好的寫出可擴(kuò)展、可管理、易維護(hù)的代碼,而這個(gè)學(xué)習(xí)的過程需要自己不斷的嘗試實(shí)際操作,理論的知識與實(shí)際結(jié)合還有很長一段距離。切記多多上手!
七、推薦閱讀
1. 重學(xué) Java 設(shè)計(jì)模式:實(shí)戰(zhàn)工廠方法模式「多種類型商品不同接口,統(tǒng)一發(fā)獎(jiǎng)服務(wù)搭建場景」2. 重學(xué) Java 設(shè)計(jì)模式:實(shí)戰(zhàn)原型模式「上機(jī)考試多套試,每人題目和答案亂序排列場景」3. 重學(xué) Java 設(shè)計(jì)模式:實(shí)戰(zhàn)橋接模式「多支付渠道(微信、支付寶)與多支付模式(刷臉、指紋)場景」4. 重學(xué) Java 設(shè)計(jì)模式:實(shí)戰(zhàn)組合模式「營銷差異化人群發(fā)券,決策樹引擎搭建場景」5. 重學(xué) Java 設(shè)計(jì)模式:實(shí)戰(zhàn)外觀模式「基于SpringBoot開發(fā)門面模式中間件,統(tǒng)一控制接口白名單場景」6. 重學(xué) Java 設(shè)計(jì)模式:實(shí)戰(zhàn)享元模式「基于Redis秒殺,提供活動(dòng)與庫存信息查詢場景」7. 重學(xué) Java 設(shè)計(jì)模式:實(shí)戰(zhàn)備忘錄模式「模擬互聯(lián)網(wǎng)系統(tǒng)上線過程中,配置文件回滾場景」