<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          「補(bǔ)課」進(jìn)行時(shí):設(shè)計(jì)模式(17)——備忘錄模式

          共 9727字,需瀏覽 20分鐘

           ·

          2020-12-07 18:09


          1. 前文匯總

          「補(bǔ)課」進(jìn)行時(shí):設(shè)計(jì)模式系列

          2. 從版本控制開始

          相信每個(gè)程序猿,每天工作都會(huì)使用版本控制工具,不管是微軟提供的 vss 還是 tfs ,又或者是開源的 svn 或者 git ,每天下班前,總歸會(huì)使用版本控制工具提交一版代碼。

          版本管理工具是讓我們?cè)诖a出問題的時(shí)候,可以方便的獲取到之前的版本進(jìn)行版本回退,尤其是在項(xiàng)目發(fā)布投運(yùn)的時(shí)候,當(dāng)出現(xiàn)問題的時(shí)候直接獲取上一個(gè)版本進(jìn)行回滾操作。

          在這個(gè)操作中間,最重要的就是保存之前的狀態(tài),那么如何保存之前的狀態(tài)?

          操作很簡單,我們可以定義一個(gè)中間變量,保留這個(gè)原始狀態(tài)。

          先定義一個(gè)版本管理 Git 類:

          public?class?Git?{
          ????private?String?state;
          ????//?版本發(fā)生改變,現(xiàn)在是?version2
          ????public?void?changeState()?{
          ????????this.state?=?"version2";
          ????}

          ????public?String?getState()?{
          ????????return?state;
          ????}

          ????public?void?setState(String?state)?{
          ????????this.state?=?state;
          ????}
          }

          然后是一個(gè)場(chǎng)景 Client 類:

          public?class?Client?{
          ????public?static?void?main(String[]?args)?{
          ????????Git?git?=?new?Git();
          ????????//?初始化版本
          ????????git.setState("version1");
          ????????System.out.println("當(dāng)前的版本信息:");
          ????????System.out.println(git.getState());
          ????????//?記錄下當(dāng)前的狀態(tài)
          ????????Git?backup?=?new?Git();
          ????????backup.setState(git.getState());
          ????????//?提交一個(gè)版本,版本進(jìn)行改變
          ????????git.changeState();
          ????????System.out.println("提交一個(gè)版本后的版本信息:");
          ????????System.out.println(git.getState());
          ????????//?回退一個(gè)版本,版本信息回滾
          ????????git.setState(backup.getState());
          ????????System.out.println("回退一個(gè)版本后的版本信息:");
          ????????System.out.println(git.getState());
          ????}
          }

          執(zhí)行結(jié)果:

          當(dāng)前的版本信息:
          version1
          提交一個(gè)版本后的版本信息:
          version2
          回退一個(gè)版本后的版本信息:
          version1

          程序運(yùn)行正確,輸出結(jié)果也是我們期望的,但是結(jié)果正確并不表示程序是合適的。

          在場(chǎng)景類 Client 類中,這個(gè)是高層模塊,現(xiàn)在卻在高層模塊中做了中間臨時(shí)變量 backup 的狀態(tài)的保持,為什么一個(gè)狀態(tài)的保存和恢復(fù)要讓高層模塊來負(fù)責(zé)呢?

          這個(gè)中間臨時(shí)變量 backup 應(yīng)該是 Git 類的職責(zé),而不是讓一個(gè)高層次的模塊來進(jìn)行定義。

          我們新建一個(gè) Memento 類,用作負(fù)責(zé)狀態(tài)的保存和備份。

          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;
          ????}
          }

          新建一個(gè) Memento ,用構(gòu)造函數(shù)來傳遞狀態(tài) state ,修改上面的 Git 類,新增兩個(gè)方法 createMemento()restoreMemento(),用來創(chuàng)建備忘錄以及恢復(fù)一個(gè)備忘錄。

          public?class?Git?{
          ????private?String?state;
          ????//?版本發(fā)生改變,現(xiàn)在是?version2
          ????public?void?changeState()?{
          ????????this.state?=?"version2";
          ????}

          ????public?String?getState()?{
          ????????return?state;
          ????}

          ????public?void?setState(String?state)?{
          ????????this.state?=?state;
          ????}
          ????//?創(chuàng)建一個(gè)備忘錄
          ????public?Memento?createMemento(String?state)?{
          ????????return?new?Memento(state);
          ????}
          ????//?恢復(fù)一個(gè)備忘錄
          ????public?void?restoreMemento(Memento?memento)?{
          ????????this.setState(memento.getState());
          ????}
          }

          修改后的場(chǎng)景類:

          public?class?Client?{
          ????public?static?void?main(String[]?args)?{
          ????????Git?git?=?new?Git();
          ????????//?初始化版本
          ????????git.setState("version1");
          ????????System.out.println("當(dāng)前的版本信息:");
          ????????System.out.println(git.getState());
          ????????//?記錄下當(dāng)前的狀態(tài)
          ????????Memento?mem?=?git.createMemento(git.getState());
          ????????//?提交一個(gè)版本,版本進(jìn)行改變
          ????????git.changeState();
          ????????System.out.println("提交一個(gè)版本后的版本信息:");
          ????????System.out.println(git.getState());
          ????????//?項(xiàng)目發(fā)布失敗,回滾狀態(tài)
          ????????git.restoreMemento(mem);
          ????????System.out.println("回退一個(gè)版本后的版本信息:");
          ????????System.out.println(git.getState());
          ????}
          }

          運(yùn)行結(jié)果和之前的案例保持一致,那么這就結(jié)束了么,當(dāng)然沒有,雖然我們?cè)?Client 中不再需要重復(fù)定義 Git 類了,但是這是對(duì)迪米特法則的一個(gè)褻瀆,它告訴我們只和朋友類通信,那這個(gè)備忘錄對(duì)象是我們必須要通信的朋友類嗎?對(duì)高層模塊來說,它最希望要做的就是創(chuàng)建一個(gè)備份點(diǎn),然后在需要的時(shí)候再恢復(fù)到這個(gè)備份點(diǎn)就成了,它不用關(guān)心到底有沒有備忘錄這個(gè)類。

          那我們可以對(duì)這個(gè)備忘錄的類再做一下包裝,創(chuàng)建一個(gè)管理類,專門用作管理這個(gè)備忘錄:

          public?class?Caretaker?{
          ????private?Memento?memento;

          ????public?Memento?getMemento()?{
          ????????return?memento;
          ????}

          ????public?void?setMemento(Memento?memento)?{
          ????????this.memento?=?memento;
          ????}
          }

          非常簡單純粹的一個(gè) JavaBean ,甭管它多簡單,只要有用就成,我們來看場(chǎng)景類如何調(diào)用:

          public?class?Client?{
          ????public?static?void?main(String[]?args)?{
          ????????Git?git?=?new?Git();
          ????????//?創(chuàng)建一個(gè)備忘錄管理者
          ????????Caretaker?caretaker?=?new?Caretaker();
          ????????//?初始化版本
          ????????git.setState("version1");
          ????????System.out.println("當(dāng)前的版本信息:");
          ????????System.out.println(git.getState());
          ????????//?記錄下當(dāng)前的狀態(tài)
          ????????caretaker.setMemento(git.createMemento(git.getState()));
          ????????//?提交一個(gè)版本,版本進(jìn)行改變
          ????????git.changeState();
          ????????System.out.println("提交一個(gè)版本后的版本信息:");
          ????????System.out.println(git.getState());
          ????????//?項(xiàng)目發(fā)布失敗,回滾狀態(tài)
          ????????git.restoreMemento(caretaker.getMemento());
          ????????System.out.println("回退一個(gè)版本后的版本信息:");
          ????????System.out.println(git.getState());
          ????}
          }

          現(xiàn)在這個(gè)備份者就類似于一個(gè)備份的倉庫管理員,創(chuàng)建一個(gè)丟進(jìn)去,需要的時(shí)候再拿出來。這就是備忘錄模式。

          3. 備忘錄模式

          3.1 定義

          備忘錄模式(Memento Pattern)提供了一種彌補(bǔ)真實(shí)世界缺陷的方法,讓“后悔藥”在程序的世界中真實(shí)可行,其定義如下:

          Without violating encapsulation,capture and externalize an object's internalstate so that the object can be restored to this state later.(在不破壞封裝性的前提下,捕獲一個(gè)對(duì)象的內(nèi)部狀態(tài),并在該對(duì)象之外保存這個(gè)狀態(tài)。這樣以后就可將該對(duì)象恢復(fù)到原先保存的狀態(tài)。)

          3.2 通用類圖

          • Originator 發(fā)起人角色:記錄當(dāng)前時(shí)刻的內(nèi)部狀態(tài),負(fù)責(zé)定義哪些屬于備份范圍的狀態(tài),負(fù)責(zé)創(chuàng)建和恢復(fù)備忘錄數(shù)據(jù)。
          • Memento 備忘錄角色:負(fù)責(zé)存儲(chǔ) Originator 發(fā)起人對(duì)象的內(nèi)部狀態(tài),在需要的時(shí)候提供發(fā)起人需要的內(nèi)部狀態(tài)。
          • Caretaker 備忘錄管理員角色:對(duì)備忘錄進(jìn)行管理、保存和提供備忘錄。

          3.3 通用代碼

          發(fā)起人:

          public?class?Originator?{
          ????private?String?state;

          ????public?String?getState()?{
          ????????return?state;
          ????}

          ????public?void?setState(String?state)?{
          ????????this.state?=?state;
          ????}
          ????//?創(chuàng)建一個(gè)備忘錄
          ????public?Memento?createMemento()?{
          ????????return?new?Memento(this.state);
          ????}
          ????//?恢復(fù)一個(gè)備忘錄
          ????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?{
          ????//?備忘錄對(duì)象
          ????private?Memento?memento;

          ????public?Memento?getMemento()?{
          ????????return?memento;
          ????}

          ????public?void?setMemento(Memento?memento)?{
          ????????this.memento?=?memento;
          ????}
          }

          場(chǎng)景類:

          public?class?Client?{
          ????public?static?void?main(String[]?args)?{
          ????????//?定義發(fā)起人
          ????????Originator?originator?=?new?Originator();
          ????????//?定義備忘錄管理員
          ????????Caretaker?caretaker?=?new?Caretaker();
          ????????//?創(chuàng)建一個(gè)備忘錄
          ????????caretaker.setMemento(originator.createMemento());
          ????????//?恢復(fù)一個(gè)備忘錄
          ????????originator.restoreMemento(caretaker.getMemento());
          ????}
          }

          4. clone 方式的備忘錄

          我們可以通過復(fù)制的方式產(chǎn)生一個(gè)對(duì)象的內(nèi)部狀態(tài),這是一個(gè)很好的辦法,發(fā)起人角色只要實(shí)現(xiàn) Cloneable 就成,比較簡單:

          public?class?Originator?implements?Cloneable?{
          ????//?內(nèi)部狀態(tài)
          ????private?String?state;

          ????public?String?getState()?{
          ????????return?state;
          ????}

          ????public?void?setState(String?state)?{
          ????????this.state?=?state;
          ????}

          ????private?Originator?backup;

          ????//?創(chuàng)建一個(gè)備忘錄
          ????public?void?createMemento()?{
          ????????this.backup?=?this.clone();
          ????}
          ????//?恢復(fù)一個(gè)備忘錄
          ????public?void?restoreMemento()?{
          ????????this.setState(this.backup.getState());
          ????}
          ????//?克隆當(dāng)前對(duì)象
          ????@Override
          ????protected?Originator?clone()?{
          ????????try?{
          ????????????return?(Originator)?super.clone();
          ????????}?catch?(CloneNotSupportedException?e)?{
          ????????????e.printStackTrace();
          ????????}
          ????????return?null;
          ????}
          }

          備忘錄管理員:

          public?class?Caretaker?{
          ????//?發(fā)起人對(duì)象
          ????private?Originator?originator;

          ????public?Originator?getOriginator()?{
          ????????return?originator;
          ????}

          ????public?void?setOriginator(Originator?originator)?{
          ????????this.originator?=?originator;
          ????}
          }

          場(chǎng)景類:

          public?class?Client?{
          ????public?static?void?main(String[]?args)?{
          ????????//?定義發(fā)起人
          ????????Originator?originator?=?new?Originator();
          ????????//?創(chuàng)建初始狀態(tài)
          ????????originator.setState("初始狀態(tài)");
          ????????System.out.println("初始狀態(tài):"?+?originator.getState());
          ????????//?創(chuàng)建備份
          ????????originator.createMemento();
          ????????//?修改狀態(tài)
          ????????originator.setState("修改后的狀態(tài)");
          ????????System.out.println("修改后的狀態(tài):"?+?originator.getState());
          ????????//?恢復(fù)狀態(tài)
          ????????originator.restoreMemento();
          ????????System.out.println("恢復(fù)后的狀態(tài):"?+?originator.getState());
          ????}
          }

          運(yùn)行結(jié)果是我們所希望的,程序精簡了很多,而且高層模塊的依賴也減少了,這正是我們期望的效果。

          但是我們來考慮一下原型模式深拷貝和淺拷貝的問題,在復(fù)雜的場(chǎng)景下它會(huì)讓我們的程序邏輯異常混亂,出現(xiàn)錯(cuò)誤也很難跟蹤。因此 Clone 方式的備忘錄模式適用于較簡單的場(chǎng)景。

          5. 多備份的備忘錄

          我們每天使用的 Windows 是可以擁有多個(gè)備份時(shí)間點(diǎn)的,系統(tǒng)出現(xiàn)問題,我們可以自由選擇需要恢復(fù)的還原點(diǎn)。

          我們上面的備忘錄模式尚且不具有這個(gè)功能,只能有一個(gè)備份,想要有多個(gè)備份也比較簡單,我們?cè)趥浞莸臅r(shí)候做一個(gè)標(biāo)記,簡單一點(diǎn)可以使用一個(gè)字符串。

          我們只要把通用代碼中的 Caretaker 管理員稍做修改就可以了:

          public?class?Caretaker?{
          ????//?容納備忘錄的容器
          ????private?Map?mementoMap?=?new?HashMap<>();

          ????public?Memento?getMemento(String?keys)?{
          ????????return?mementoMap.get(keys);
          ????}

          ????public?void?setMemento(String?key,?Memento?memento)?{
          ????????this.mementoMap.put(key,?memento);
          ????}
          }

          對(duì)場(chǎng)景類做部分修改:

          public?class?Client?{
          ????public?static?void?main(String[]?args)?{
          ????????//?定義發(fā)起人
          ????????Originator?originator?=?new?Originator();
          ????????//?定義備忘錄管理員
          ????????Caretaker?caretaker?=?new?Caretaker();
          ????????//?創(chuàng)建兩個(gè)備忘錄
          ????????caretaker.setMemento("001",?originator.createMemento());
          ????????caretaker.setMemento("002",?originator.createMemento());
          ????????//?恢復(fù)一個(gè)指定的備忘錄
          ????????originator.restoreMemento(caretaker.getMemento("002"));
          ????}
          }

          6. 更好的封裝

          在系統(tǒng)管理上,一個(gè)備份的數(shù)據(jù)是完全、絕對(duì)不能修改的,它保證數(shù)據(jù)的潔凈,避免數(shù)據(jù)污染而使備份失去意義。

          在我們的程序中也有著同樣的問題,備份是不能被褚篡改的,那么也就是需要縮小備忘錄的訪問權(quán)限,保證只有發(fā)起人可讀就可以了。

          這個(gè)很簡單,直接使用內(nèi)置類就可以了:

          public?class?Originator?{
          ????private?String?state;

          ????public?String?getState()?{
          ????????return?state;
          ????}

          ????public?void?setState(String?state)?{
          ????????this.state?=?state;
          ????}
          ????//?創(chuàng)建一個(gè)備忘錄
          ????public?IMemento?createMemento()?{
          ????????return?new?Memento(this.state);
          ????}
          ????//?恢復(fù)一個(gè)備忘錄
          ????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;
          ????????}
          ????}
          }

          這里使用了一個(gè) IMemento 接口,這個(gè)接口實(shí)際上是一個(gè)空接口:

          public?interface?IMemento?{
          }

          這個(gè)空接口的作用是用作公共的訪問權(quán)限。

          下面看一下備忘錄管理者的變化:

          public?class?Caretaker?{
          ????//?備忘錄對(duì)象
          ????private?IMemento?memento;

          ????public?IMemento?getMemento()?{
          ????????return?memento;
          ????}

          ????public?void?setMemento(IMemento?memento)?{
          ????????this.memento?=?memento;
          ????}
          }

          上面這段示例全部通過接口訪問,如果我們想訪問它的屬性貌似是無法訪問到了。

          但是安全是相對(duì)的,沒有絕對(duì)的安全,我們可以使用 refelect 反射修改 Memento 的數(shù)據(jù)。

          在這里我們使用了一個(gè)新的設(shè)計(jì)方法:雙接口設(shè)計(jì),我們的一個(gè)類可以實(shí)現(xiàn)多個(gè)接口,在系統(tǒng)設(shè)計(jì)時(shí),如果考慮對(duì)象的安全問題,則可以提供兩個(gè)接口,一個(gè)是業(yè)務(wù)的正常接口,實(shí)現(xiàn)必要的業(yè)務(wù)邏輯,叫做寬接口;另外一個(gè)接口是一個(gè)空接口,什么方法都沒有,其目的是提供給子系統(tǒng)外的模塊訪問,比如容器對(duì)象,這個(gè)叫做窄接口,由于窄接口中沒有提供任何操縱數(shù)據(jù)的方法,因此相對(duì)來說比較安全。





          感謝閱讀


          瀏覽 24
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  中文丰满亲子伦 | 日韩AV小电影 | 麻豆1级 麻豆操比 | 夜色福利av | 日本激情视频小说 |