欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Go設(shè)計模式之備忘錄模式圖文詳解

 更新時間:2023年08月02日 08:28:04   作者:demo007x  
備忘錄模式是一種行為設(shè)計模式, 允許在不暴露對象實現(xiàn)細(xì)節(jié)的情況下保存和恢復(fù)對象之前的狀態(tài),本文主要通過一些圖片來給大家介紹一下Go的備忘錄模式,需要的朋友可以參考下

備忘錄模式

亦稱: 快照、Snapshot、Memento

意圖

備忘錄模式是一種行為設(shè)計模式, 允許在不暴露對象實現(xiàn)細(xì)節(jié)的情況下保存和恢復(fù)對象之前的狀態(tài)。

問題

假如你正在開發(fā)一款文字編輯器應(yīng)用程序。 除了簡單的文字編輯功能外, 編輯器中還要有設(shè)置文本格式和插入內(nèi)嵌圖片等功能。

后來, 你決定讓用戶能撤銷施加在文本上的任何操作。 這項功能在過去幾年里變得十分普遍, 因此用戶期待任何程序都有這項功能。 你選擇采用直接的方式來實現(xiàn)該功能: 程序在執(zhí)行任何操作前會記錄所有的對象狀態(tài), 并將其保存下來。 當(dāng)用戶此后需要撤銷某個操作時, 程序?qū)臍v史記錄中獲取最近的快照, 然后使用它來恢復(fù)所有對象的狀態(tài)。

程序在執(zhí)行操作前保存所有對象的狀態(tài)快照, 稍后可通過快照將對象恢復(fù)到之前的狀態(tài)。

讓我們來思考一下這些狀態(tài)快照。 首先, 到底該如何生成一個快照呢? 很可能你會需要遍歷對象的所有成員變量并將其數(shù)值復(fù)制保存。 但只有當(dāng)對象對其內(nèi)容沒有嚴(yán)格訪問權(quán)限限制的情況下, 你才能使用該方式。 不過很遺憾, 絕大部分對象會使用私有成員變量來存儲重要數(shù)據(jù), 這樣別人就無法輕易查看其中的內(nèi)容。

現(xiàn)在我們暫時忽略這個問題, 假設(shè)對象都像嬉皮士一樣: 喜歡開放式的關(guān)系并會公開其所有狀態(tài)。 盡管這種方式能夠解決當(dāng)前問題, 讓你可隨時生成對象的狀態(tài)快照, 但這種方式仍存在一些嚴(yán)重問題。 未來你可能會添加或刪除一些成員變量。 這聽上去很簡單, 但需要對負(fù)責(zé)復(fù)制受影響對象狀態(tài)的類進(jìn)行更改。

如何復(fù)制對象的私有狀態(tài)?

還有更多問題。 讓我們來考慮編輯器 (Editor) 狀態(tài)的實際 “快照”, 它需要包含哪些數(shù)據(jù)? 至少必須包含實際的文本、 光標(biāo)坐標(biāo)和當(dāng)前滾動條位置等。 你需要收集這些數(shù)據(jù)并將其放入特定容器中, 才能生成快照。

你很可能會將大量的容器對象存儲在歷史記錄列表中。 這樣一來, 容器最終大概率會成為同一個類的對象。 這個類中幾乎沒有任何方法, 但有許多與編輯器狀態(tài)一一對應(yīng)的成員變量。 為了讓其他對象能保存或讀取快照, 你很可能需要將快照的成員變量設(shè)為公有。 無論這些狀態(tài)是否私有, 其都將暴露一切編輯器狀態(tài)。 其他類會對快照類的每個小改動產(chǎn)生依賴, 除非這些改動僅存在于私有成員變量或方法中, 而不會影響外部類。

我們似乎走進(jìn)了一條死胡同: 要么會暴露類的所有內(nèi)部細(xì)節(jié)而使其過于脆弱; 要么會限制對其狀態(tài)的訪問權(quán)限而無法生成快照。 那么, 我們還有其他方式來實現(xiàn) “撤銷” 功能嗎?

解決方案

我們剛才遇到的所有問題都是封裝 “破損” 造成的。 一些對象試圖超出其職責(zé)范圍的工作。 由于在執(zhí)行某些行為時需要獲取數(shù)據(jù), 所以它們侵入了其他對象的私有空間, 而不是讓這些對象來完成實際的工作。

備忘錄模式將創(chuàng)建狀態(tài)快照 (Snapshot) 的工作委派給實際狀態(tài)的擁有者原發(fā)器 (Originator) 對象。 這樣其他對象就不再需要從 “外部” 復(fù)制編輯器狀態(tài)了, 編輯器類擁有其狀態(tài)的完全訪問權(quán), 因此可以自行生成快照。

模式建議將對象狀態(tài)的副本存儲在一個名為備忘錄 (Memento) 的特殊對象中。 除了創(chuàng)建備忘錄的對象外, 任何對象都不能訪問備忘錄的內(nèi)容。 其他對象必須使用受限接口與備忘錄進(jìn)行交互, 它們可以獲取快照的元數(shù)據(jù) (創(chuàng)建時間和操作名稱等), 但不能獲取快照中原始對象的狀態(tài)。

原發(fā)器擁有對備忘錄的完全訪問權(quán)限, 負(fù)責(zé)人則只能訪問元數(shù)據(jù)。

這種限制策略允許你將備忘錄保存在通常被稱為負(fù)責(zé)人 (Caretakers) 的對象中。 由于負(fù)責(zé)人僅通過受限接口與備忘錄互動, 故其無法修改存儲在備忘錄內(nèi)部的狀態(tài)。 同時, 原發(fā)器擁有對備忘錄所有成員的訪問權(quán)限, 從而能隨時恢復(fù)其以前的狀態(tài)。

在文字編輯器的示例中, 我們可以創(chuàng)建一個獨立的歷史 (History) 類作為負(fù)責(zé)人。 編輯器每次執(zhí)行操作前, 存儲在負(fù)責(zé)人中的備忘錄棧都會生長。 你甚至可以在應(yīng)用的 UI 中渲染該棧, 為用戶顯示之前的操作歷史。

當(dāng)用戶觸發(fā)撤銷操作時, 歷史類將從棧中取回最近的備忘錄, 并將其傳遞給編輯器以請求進(jìn)行回滾。 由于編輯器擁有對備忘錄的完全訪問權(quán)限, 因此它可以使用從備忘錄中獲取的數(shù)值來替換自身的狀態(tài)。

備忘錄模式結(jié)構(gòu)

基于嵌套類的實現(xiàn)

該模式的經(jīng)典實現(xiàn)方式依賴于許多流行編程語言 (例如 C++、 C# 和 Java) 所支持的嵌套類。

  • 原發(fā)器 (Originator) 類可以生成自身狀態(tài)的快照, 也可以在需要時通過快照恢復(fù)自身狀態(tài)。

  • 備忘錄 (Memento) 是原發(fā)器狀態(tài)快照的值對象 (value object)。 通常做法是將備忘錄設(shè)為不可變的, 并通過構(gòu)造函數(shù)一次性傳遞數(shù)據(jù)。

  • 負(fù)責(zé)人 (Caretaker) 僅知道 “何時” 和 “為何” 捕捉原發(fā)器的狀態(tài), 以及何時恢復(fù)狀態(tài)。

    負(fù)責(zé)人通過保存?zhèn)渫洍碛涗浽l(fā)器的歷史狀態(tài)。 當(dāng)原發(fā)器需要回溯歷史狀態(tài)時, 負(fù)責(zé)人將從棧中獲取最頂部的備忘錄, 并將其傳遞給原發(fā)器的恢復(fù) (restoration) 方法。

  • 在該實現(xiàn)方法中, 備忘錄類將被嵌套在原發(fā)器中。 這樣原發(fā)器就可訪問備忘錄的成員變量和方法, 即使這些方法被聲明為私有。 另一方面, 負(fù)責(zé)人對于備忘錄的成員變量和方法的訪問權(quán)限非常有限: 它們只能在棧中保存?zhèn)渫洠?而不能修改其狀態(tài)。

基于中間接口的實現(xiàn)

另外一種實現(xiàn)方法適用于不支持嵌套類的編程語言 (沒錯, 我說的就是 PHP)。

  • 在沒有嵌套類的情況下, 你可以規(guī)定負(fù)責(zé)人僅可通過明確聲明的中間接口與備忘錄互動, 該接口僅聲明與備忘錄元數(shù)據(jù)相關(guān)的方法, 限制其對備忘錄成員變量的直接訪問權(quán)限。
  • 另一方面, 原發(fā)器可以直接與備忘錄對象進(jìn)行交互, 訪問備忘錄類中聲明的成員變量和方法。 這種方式的缺點在于你需要將備忘錄的所有成員變量聲明為公有。

封裝更加嚴(yán)格的實現(xiàn)

如果你不想讓其他類有任何機(jī)會通過備忘錄來訪問原發(fā)器的狀態(tài), 那么還有另一種可用的實現(xiàn)方式。

  • 這種實現(xiàn)方式允許存在多種不同類型的原發(fā)器和備忘錄。 每種原發(fā)器都和其相應(yīng)的備忘錄類進(jìn)行交互。 原發(fā)器和備忘錄都不會將其狀態(tài)暴露給其他類。
  • 負(fù)責(zé)人此時被明確禁止修改存儲在備忘錄中的狀態(tài)。 但負(fù)責(zé)人類將獨立于原發(fā)器, 因為此時恢復(fù)方法被定義在了備忘錄類中。
  • 每個備忘錄將與創(chuàng)建了自身的原發(fā)器連接。 原發(fā)器會將自己及狀態(tài)傳遞給備忘錄的構(gòu)造函數(shù)。 由于這些類之間的緊密聯(lián)系, 只要原發(fā)器定義了合適的設(shè)置器 (setter), 備忘錄就能恢復(fù)其狀態(tài)。

偽代碼

本例結(jié)合使用了命令模式與備忘錄模式, 可保存復(fù)雜文字編輯器的狀態(tài)快照, 并能在需要時從快照中恢復(fù)之前的狀態(tài)。

保存文字編輯器狀態(tài)的快照。

命令 (command) 對象將作為負(fù)責(zé)人, 它們會在執(zhí)行與命令相關(guān)的操作前獲取編輯器的備忘錄。 當(dāng)用戶試圖撤銷最近的命令時, 編輯器可以使用保存在命令中的備忘錄來將自身回滾到之前的狀態(tài)。

備忘錄類沒有聲明任何公有的成員變量、 獲取器 (getter) 和設(shè)置器, 因此沒有對象可以修改其內(nèi)容。 備忘錄與創(chuàng)建自己的編輯器相連接, 這使得備忘錄能夠通過編輯器對象的設(shè)置器傳遞數(shù)據(jù), 恢復(fù)與其相連接的編輯器的狀態(tài)。 由于備忘錄與特定的編輯器對象相連接, 程序可以使用中心化的撤銷棧實現(xiàn)對多個獨立編輯器窗口的支持。

// 原發(fā)器中包含了一些可能會隨時間變化的重要數(shù)據(jù)。它還定義了在備忘錄中保存
// 自身狀態(tài)的方法,以及從備忘錄中恢復(fù)狀態(tài)的方法。
class Editor is
    private field text, curX, curY, selectionWidth
    method setText(text) is
        this.text = text
    method setCursor(x, y) is
        this.curX = x
        this.curY = y
    method setSelectionWidth(width) is
        this.selectionWidth = width
    // 在備忘錄中保存當(dāng)前的狀態(tài)。
    method createSnapshot():Snapshot is
        // 備忘錄是不可變的對象;因此原發(fā)器會將自身狀態(tài)作為參數(shù)傳遞給備忘
        // 錄的構(gòu)造函數(shù)。
        return new Snapshot(this, text, curX, curY, selectionWidth)
// 備忘錄類保存有編輯器的過往狀態(tài)。
class Snapshot is
    private field editor: Editor
    private field text, curX, curY, selectionWidth
    constructor Snapshot(editor, text, curX, curY, selectionWidth) is
        this.editor = editor
        this.text = text
        this.curX = x
        this.curY = y
        this.selectionWidth = selectionWidth
    // 在某一時刻,編輯器之前的狀態(tài)可以使用備忘錄對象來恢復(fù)。
    method restore() is
        editor.setText(text)
        editor.setCursor(curX, curY)
        editor.setSelectionWidth(selectionWidth)
// 命令對象可作為負(fù)責(zé)人。在這種情況下,命令會在修改原發(fā)器狀態(tài)之前獲取一個
// 備忘錄。當(dāng)需要撤銷時,它會從備忘錄中恢復(fù)原發(fā)器的狀態(tài)。
class Command is
    private field backup: Snapshot
    method makeBackup() is
        backup = editor.createSnapshot()
    method undo() is
        if (backup != null)
            backup.restore()
    // ……

備忘錄模式適合應(yīng)用場景

當(dāng)你需要創(chuàng)建對象狀態(tài)快照來恢復(fù)其之前的狀態(tài)時, 可以使用備忘錄模式。

備忘錄模式允許你復(fù)制對象中的全部狀態(tài) (包括私有成員變量), 并將其獨立于對象進(jìn)行保存。 盡管大部分人因為 “撤銷” 這個用例才記得該模式, 但其實它在處理事務(wù) (比如需要在出現(xiàn)錯誤時回滾一個操作) 的過程中也必不可少。

當(dāng)直接訪問對象的成員變量、 獲取器或設(shè)置器將導(dǎo)致封裝被突破時, 可以使用該模式。

備忘錄讓對象自行負(fù)責(zé)創(chuàng)建其狀態(tài)的快照。 任何其他對象都不能讀取快照, 這有效地保障了數(shù)據(jù)的安全性。

實現(xiàn)方式

  • 確定擔(dān)任原發(fā)器角色的類。 重要的是明確程序使用的一個原發(fā)器中心對象, 還是多個較小的對象。

  • 創(chuàng)建備忘錄類。 逐一聲明對應(yīng)每個原發(fā)器成員變量的備忘錄成員變量。

  • 將備忘錄類設(shè)為不可變。 備忘錄只能通過構(gòu)造函數(shù)一次性接收數(shù)據(jù)。 該類中不能包含設(shè)置器。

  • 如果你所使用的編程語言支持嵌套類, 則可將備忘錄嵌套在原發(fā)器中; 如果不支持, 那么你可從備忘錄類中抽取一個空接口, 然后讓其他所有對象通過接口來引用備忘錄。 你可在該接口中添加一些元數(shù)據(jù)操作, 但不能暴露原發(fā)器的狀態(tài)。

  • 在原發(fā)器中添加一個創(chuàng)建備忘錄的方法。 原發(fā)器必須通過備忘錄構(gòu)造函數(shù)的一個或多個實際參數(shù)來將自身狀態(tài)傳遞給備忘錄。

    該方法返回結(jié)果的類型必須是你在上一步中抽取的接口 (如果你已經(jīng)抽取了)。 實際上, 創(chuàng)建備忘錄的方法必須直接與備忘錄類進(jìn)行交互。

  • 在原發(fā)器類中添加一個用于恢復(fù)自身狀態(tài)的方法。 該方法接受備忘錄對象作為參數(shù)。 如果你在之前的步驟中抽取了接口, 那么可將接口作為參數(shù)的類型。 在這種情況下, 你需要將輸入對象強(qiáng)制轉(zhuǎn)換為備忘錄, 因為原發(fā)器需要擁有對該對象的完全訪問權(quán)限。

  • 無論負(fù)責(zé)人是命令對象、 歷史記錄或其他完全不同的東西, 它都必須要知道何時向原發(fā)器請求新的備忘錄、 如何存儲備忘錄以及何時使用特定備忘錄來對原發(fā)器進(jìn)行恢復(fù)。

  • 負(fù)責(zé)人與原發(fā)器之間的連接可以移動到備忘錄類中。 在本例中, 每個備忘錄都必須與創(chuàng)建自己的原發(fā)器相連接。 恢復(fù)方法也可以移動到備忘錄類中, 但只有當(dāng)備忘錄類嵌套在原發(fā)器中, 或者原發(fā)器類提供了足夠多的設(shè)置器并可對其狀態(tài)進(jìn)行重寫時, 這種方式才能實現(xiàn)。

備忘錄模式優(yōu)缺點

  • 你可以在不破壞對象封裝情況的前提下創(chuàng)建對象狀態(tài)快照。

  • 你可以通過讓負(fù)責(zé)人維護(hù)原發(fā)器狀態(tài)歷史記錄來簡化原發(fā)器代碼。

  • 如果客戶端過于頻繁地創(chuàng)建備忘錄, 程序?qū)⑾拇罅績?nèi)存。

  • 負(fù)責(zé)人必須完整跟蹤原發(fā)器的生命周期, 這樣才能銷毀棄用的備忘錄。

  • 絕大部分動態(tài)編程語言 (例如 PHP、 Python 和 JavaScript) 不能確保備忘錄中的狀態(tài)不被修改。

代碼示例

Go設(shè)計模式之備忘錄模式講解和代碼示例_Golang_腳本之家 (jb51.net)

以上就是Go設(shè)計模式之備忘錄模式圖文詳解的詳細(xì)內(nèi)容,更多關(guān)于Go備忘錄模式的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Golang HTTP 服務(wù)平滑重啟及升級的思路

    Golang HTTP 服務(wù)平滑重啟及升級的思路

    Golang HTTP服務(wù)在上線時,需要重新編譯可執(zhí)行文件,關(guān)閉正在運行的進(jìn)程,然后再啟動新的運行進(jìn)程。這篇文章主要介紹了Golang HTTP 服務(wù)平滑重啟及升級,需要的朋友可以參考下
    2020-04-04
  • Go語言使用ioutil.ReadAll函數(shù)需要注意基本說明

    Go語言使用ioutil.ReadAll函數(shù)需要注意基本說明

    這篇文章主要為大家介紹了Go語言使用ioutil.ReadAll函數(shù)需要注意基本說明,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-07-07
  • Go 1.21新增的slices包中切片函數(shù)用法詳解

    Go 1.21新增的slices包中切片函數(shù)用法詳解

    Go 1.21新增的 slices 包提供了很多和切片相關(guān)的函數(shù),可以用于任何類型的切片,本文通過代碼示例為大家介紹了部分切片函數(shù)的具體用法,感興趣的小伙伴可以了解一下
    2023-08-08
  • 在golang中使用cel的用法詳解

    在golang中使用cel的用法詳解

    CEL?是一種非圖靈完備的表達(dá)式語言?,旨在快速、可移植且執(zhí)行安全,CEL?可以單獨使用,也可以嵌入到其他的產(chǎn)品中,本文將給大家介紹一下golang中如何使用cel,需要的朋友可以參考下
    2023-11-11
  • 一文搞懂Go語言中defer關(guān)鍵字的使用

    一文搞懂Go語言中defer關(guān)鍵字的使用

    defer是golang中用的比較多的一個關(guān)鍵字,也是go面試題里經(jīng)常出現(xiàn)的問題。今天就來整理一下關(guān)于defer的學(xué)習(xí)使用,希望對需要的朋友有所幫助
    2022-09-09
  • Go文件操作(新建打開寫入讀取刪除關(guān)閉)學(xué)習(xí)筆記

    Go文件操作(新建打開寫入讀取刪除關(guān)閉)學(xué)習(xí)筆記

    這篇文章主要為大家介紹了Go文件操作(新建打開寫入讀取刪除關(guān)閉)學(xué)習(xí)筆記,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2024-01-01
  • go語言中proto文件的使用

    go語言中proto文件的使用

    在Go語言編程中,.proto文件用于定義Protocol?Buffers數(shù)據(jù)結(jié)構(gòu)和服務(wù),是實現(xiàn)跨語言通信和高效序列化的關(guān)鍵,具有一定的參考價值,感興趣的可以了解一下
    2024-10-10
  • Golang中sync.Mutex的源碼分析

    Golang中sync.Mutex的源碼分析

    這篇文章將帶大家從源碼分析一下Golang中sync.Mutex的使用,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)Golang有一定的幫助,需要的可以參考一下
    2023-03-03
  • Go語言如何實現(xiàn)TCP通信詳解

    Go語言如何實現(xiàn)TCP通信詳解

    go里面實現(xiàn)tcp沒有像之前寫的C++那些那么麻煩,在C++里面要先創(chuàng)建套接字,然后綁定ip地址,go里面直接就一個函數(shù)建立套接字,然后在進(jìn)行通信就可以了,下面這篇文章主要給大家介紹了關(guān)于Go語言如何實現(xiàn)TCP通信的相關(guān)資料,需要的朋友可以參考下
    2023-01-01
  • Golang中的泛型你真的了解嗎

    Golang中的泛型你真的了解嗎

    Golang?在?1.18?版本更新后引入了泛型,這是一個重要的更新,Gopher?萬眾矚目,為?Golang?帶來了更多的靈活性和可重用性,今天,我們將深入探討泛型的概念、為什么需要泛型、泛型的語法,并探討如何在實踐中使用它
    2023-05-05

最新評論