對備忘錄模式的理解

對備忘錄模式的理解

    • 一、場景
      • 1、題目【[來源](https://kamacoder.com/problempage.php?pid=1095)】
        • 1.1 題目描述
        • 1.2 輸入描述
        • 1.3 輸出描述
        • 1.4 輸入示例
        • 1.5 輸出示例
      • 2、理解需求
    • 二、不采用備忘錄設計模式
      • 1、代碼
      • 2、問題
      • 3、錯誤的備忘錄模式
    • 三、采用備忘錄設計模式
      • 1、代碼
        • 1.1 Originator(原發器)
        • 1.2 Memento(備忘錄)
        • 1.3 Caretaker(負責人)
        • 1.4 客戶端
      • 2、思考

一、場景

1、題目【來源】

1.1 題目描述

小明正在設計一個簡單的計數器應用,支持增加(Increment)和減少(Decrement)操作,以及撤銷(Undo)和重做(Redo)操作,請你使用備忘錄模式幫他實現。

1.2 輸入描述

輸入包含若干行,每行包含一個字符串,表示計數器應用的操作,操作包括 “Increment”、“Decrement”、“Undo” 和 “Redo”。

1.3 輸出描述

對于每個 “Increment” 和 “Decrement” 操作,輸出當前計數器的值,計數器數值從0開始 對于每個 “Undo” 操作,輸出撤銷后的計數器值。 對于每個 “Redo” 操作,輸出重做后的計數器值。

1.4 輸入示例
Increment
Increment
Decrement
Undo
Redo
Increment
1.5 輸出示例
1
2
1
2
1
2

2、理解需求

  • 增加(Increment)和減少(Decrement)操作比較好理解,不贅述了。

  • 重點理解下:撤銷(Undo)和重做(Redo)操作。

    ?image?

    • 一般編輯器,都支持Undo和Redo操作。

    • Undo操作:因為操作導致值發生變化,例如,0變成1。我們需要記下變化的值,這樣才方便用戶回退。

      • 很明顯,應該用棧來記錄。例如:0 -> 1 -> 2 -> 3。 當前處于3,接下來Undo,應該從3變成2。也就是把3從棧中彈出。
    • Redo操作:依然基于“0 -> 1 -> 2 -> 3”進行說明,當前處于3,用戶Undo后,3變成2,接下來,用戶Redo了,也就是希望2又變回3。

      • 也就是,我們需要記錄Undo棧中彈出來的值。很顯然,也是一個棧。

二、不采用備忘錄設計模式

1、代碼

public class Main {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);int res = 0;// 棧Deque<Integer> undoStack = new ArrayDeque<>();undoStack.push(res);Deque<Integer> redoStack = new ArrayDeque<>();while (scanner.hasNextLine()) {String command = scanner.nextLine();res = runCommand(command, res, undoStack, redoStack);System.out.println(res);}}private static Integer runCommand(String command, Integer res, Deque<Integer> undoStack, Deque<Integer> redoStack) {if ("Increment".equals(command)) {res += 1;undoStack.push(res);return res;} else if ("Decrement".equals(command)) {res -= 1;undoStack.push(res);return res;} else if ("Undo".equals(command)) {if (undoStack.size() == 1) {// 相當于還沒有做任何操作,用戶就執行了Undoreturn undoStack.peek();} else if (undoStack.size() > 1) {Integer value = undoStack.pop();redoStack.push(value);return undoStack.peek();}} else if ("Redo".equals(command)) {if (!redoStack.isEmpty()) {Integer value = redoStack.pop();undoStack.push(value);return value;}}return res;}
}

2、問題

  • Increment等操作是客戶端(main方法)的命令,客戶端不應該看到undoStack、redoStack等數據。

    • 上面的寫法是典型的面向過程開發,我們需要使用面向對象開發。

      ?image?

  • 很顯然,我們需要設計一個Calculator。

    • public class Calculator {private int value;public Calculator() {this.value = 0;}public Integer runCommand(String command) {return null;}
      }public class Main {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);Calculator calculator = new Calculator();while (scanner.hasNextLine()) {String command = scanner.nextLine();Integer res = calculator.runCommand(command);System.out.println(res);}}
      }
      
  • 為了實現Undo、Redo,這個類的對象有一個特點,需要保存和恢復對象之前的狀態。

    • 備忘錄模式是一種行為設計模式, 允許在不暴露對象實現細節的情況下保存和恢復對象之前的狀態。[先有場景,后有設計模式]

3、錯誤的備忘錄模式

public class Calculator {private int value;private Deque<Integer> undoStack;private Deque<Integer> redoStack;public Calculator() {this.value = 0;this.undoStack = new ArrayDeque<>();undoStack.push(value);this.redoStack = new ArrayDeque<>();}public Integer runCommand(String command) {if ("Increment".equals(command)) {value += 1;undoStack.push(value);return value;} else if ("Decrement".equals(command)) {value -= 1;undoStack.push(value);return value;} else if ("Undo".equals(command)) {if (undoStack.size() == 1) {// 相當于還沒有做任何操作,用戶就執行了Undoreturn undoStack.peek();} else if (undoStack.size() > 1) {Integer v = undoStack.pop();redoStack.push(v);return undoStack.peek();}} else if ("Redo".equals(command)) {if (!redoStack.isEmpty()) {Integer v = redoStack.pop();undoStack.push(v);return v;}}return value;}
}
  • Calculator這個類是違背單一職責的,按照備忘錄模式的經典設計,應該具有3個角色:

    • Originator(原發器):狀態持有者,并且可以請求保存狀態和恢復狀態。
    • Memento(備忘錄):負責保存狀態和恢復狀態。
    • Caretaker(負責人):負責管理備忘錄。

三、采用備忘錄設計模式

1、代碼

1.1 Originator(原發器)
public class Counter {private int value;public Counter() {this.value = 0;}public int getValue() {return value;}public void increment() {this.value++;}public void decrement() {this.value--;}public Memento createMemento() {return new Memento(this.value);}public void restoreMemento(Memento memento) {this.value = memento.getValue();}
}
1.2 Memento(備忘錄)
public class Memento {private int value;public Memento(int value) {this.value = value;}public int getValue() {return value;}
}
1.3 Caretaker(負責人)
public class Calculator {private Counter counter;private Deque<Memento> undoStack;private Deque<Memento> redoStack;public Calculator() {counter = new Counter();undoStack = new ArrayDeque<>();redoStack = new ArrayDeque<>();}public Integer runCommand(String command) {if (command.equals("Increment")) {counter.increment();undoStack.push(counter.createMemento());redoStack.clear(); // redoStack是專門用來記錄undoStack彈出的狀態的,undoStack放入新狀態后,redoStack里面的狀態就無效了} else if (command.equals("Decrement")) {counter.decrement();undoStack.push(counter.createMemento());redoStack.clear();} else if (command.equals("Undo")) {if (!undoStack.isEmpty()) {Memento memento = undoStack.pop();redoStack.push(memento);counter.restoreMemento(undoStack.peek());}} else if (command.equals("Redo")) {if (!redoStack.isEmpty()) {Memento memento = redoStack.pop();counter.restoreMemento(memento);undoStack.push(memento);}} else {throw new RuntimeException("Unknown command");}return counter.getValue();}
}
1.4 客戶端
public class Main {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);Calculator calculator = new Calculator();while (scanner.hasNextLine()) {String command = scanner.nextLine();Integer res = calculator.runCommand(command);System.out.println(res);}}
}

2、思考

  • 相比“3、錯誤的備忘錄模式”,每個類的職責更單一一些。但,Memento好麻煩啊。相當于把Counter的字段復制了一遍。以后Counter加一個字段,Memento就要補一個字段。太麻煩了。

  • 一種不錯的解決辦法是:序列化。

  • public class Counter {private int value;public Counter() {}public void setValue(int value) {this.value = value;}public int getValue() {return value;}public void increment() {this.value++;}public void decrement() {this.value--;}public void restoreMemento(Memento memento) {String backup = memento.getBackup();Counter tmpCounter = JSON.parseObject(backup, Counter.class);this.value = tmpCounter.value;}public Memento createMemento() {String backup = JSON.toJSONString(this);return new Memento(backup);}
    }public class Memento {private String backup;public Memento(String backup) {this.backup = backup;}public String getBackup() {return this.backup;}
    }
    

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/75618.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/75618.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/75618.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

86.方便的double轉string屬性 C#例子 WPF例子

在C#開發中&#xff0c;屬性封裝是一種常見的設計模式&#xff0c;它可以幫助我們更好地控制數據的訪問和修改&#xff0c;同時提供更靈活的功能擴展。今天&#xff0c;我們就來探討一個簡單而優雅的屬性封裝示例&#xff1a;Power 和 PowerFormatted。 1. 問題背景 在實際開…

bun 版本管理工具 bum 安裝與使用

在使用 node 的過程中&#xff0c;我們可能會因為版本更新或者不同項目的要求而頻繁切換 node 版本&#xff0c;或者是希望使用更簡單的方式安裝不同版本的 node&#xff0c;這個時候我們一般會用到 nvm 或者類似的工具。 在我嘗試使用 bun 的時候&#xff0c;安裝前第一個想到…

GRE,MGRE

GRE&#xff1a;靜態過程&#xff0c;有局限性 R1 &#xff1a; [r1]interface Tunnel 0/0/0 --- 創建一個虛擬的隧道接口 [r1-Tunnel0/0/0]ip address 192.168.3.1 24 --- 給隧道接口分配一個 IP 地址 [r1-Tunnel0/0/0]tunnel-protocol gre --- 定義接口的封裝方式 [r1-Tun…

游戲無法啟動?XINPUT1_3.dll 丟失的終極解決方案

當你興奮地啟動一款新游戲時&#xff0c;突然彈出一個錯誤提示——‘程序無法啟動&#xff0c;因為計算機中丟失 XINPUT1_3.dll’。這種問題在 PC 玩家中非常常見&#xff0c;尤其是運行一些較老的游戲時。XINPUT1_3.dll 是 DirectX 運行庫的關鍵組件&#xff0c;缺失會導致游戲…

用大語言模型學文學常識

李白的詩句“右軍本清真”中的“清真”并非指伊斯蘭教信仰&#xff0c;而是對王羲之&#xff08;王右軍&#xff09;人格和藝術境界的贊美。以下是對這一問題的詳細分析&#xff1a; “清真”的古代含義 在魏晉至唐代的語境中&#xff0c;“清真”一詞多用于形容人的品格高潔、…

css炫酷的3D水波紋文字效果實現詳解

炫酷的3D水波紋文字效果實現詳解 這里寫目錄標題 炫酷的3D水波紋文字效果實現詳解項目概述技術棧核心實現1. 基礎布局2. 漸變背景3. 文字效果實現3.1 基礎樣式3.2 文字漂浮動畫 4. 水波紋效果4.1 模糊效果4.2 水波動畫 5. 交互效果 技術要點項目難點與解決方案總結 項目概述 在…

八、重學C++—動態多態(運行期)

上一章節&#xff1a; 七、重學C—靜態多態&#xff08;編譯期&#xff09;-CSDN博客https://blog.csdn.net/weixin_36323170/article/details/146999362?spm1001.2014.3001.5502 本章節代碼&#xff1a; cpp/dynamicPolymorphic.cpp CuiQingCheng/cppstudy - 碼云 - 開源中…

eventEmitter實現

沒有做任何異常處理,簡單模擬實現 事件對象的每一個事件都對應一個數組 /*__events {"事件1":[cb1,cb2],"事件2":[cb3,cb4],"事件3":[...],"事件4":[...],};*/class E{__events {};constructor(){}//注冊監聽回調on(type , callbac…

Mysql 中 B 樹 vs B+ 樹

&#x1f333; 什么是 B樹 和 B樹&#xff1f; 它們都是多路平衡查找樹&#xff08;M-Way Search Tree&#xff09;&#xff0c;用于提升磁盤讀寫效率&#xff0c;常用于數據庫&#xff08;如 MySQL&#xff09;、操作系統中的索引結構。 &#x1f50d; B樹 和 B樹 的核心區別…

藍橋云客---九宮幻方

1.九宮幻方 - 藍橋云課 九宮幻方 題目描述 小明最近在教鄰居家的小朋友小學奧數&#xff0c;而最近正好講述到了三階幻方這個部分&#xff0c;三階幻方指的是將1~9不重復的填入一個3 * 3的矩陣當中&#xff0c;使得每一行、每一列和每一條對角線的和都是相同的。 三階幻方又…

OrangePi5Plus開發板不能正確識別USB 3.0 設備 (綠聯HUB和Camera)

1、先插好上電&#xff08;可正確識別&#xff09; 2、上電開機后插入USB 3.0 設備&#xff0c;報錯如下&#xff0c;只能檢測到USB2.0--480M&#xff0c;識別不到USB3.0-5Gbps&#xff0c;重新插拔也不行 Apr 4 21:30:00 orangepi5plus kernel: [ 423.575966] usb 5-1: re…

LiveData 和 MutableLiveData 的區別

LiveData 和 MutableLiveData 的區別 主要在于是否可以修改數據&#xff0c;但它們的工作原理基本相同。下面我們深入對比它們的行為、特性&#xff0c;以及它們在 ViewModel 和 UI 層中的使用方式。 1. LiveData 和 MutableLiveData 的基本區別 特性LiveDataMutableLiveData可…

SDK中窗口調用

存在窗口A和B的win32程序 , 當點擊窗口A中的按鈕后會彈出窗口B #include <windows.h>// 窗口 B 的窗口過程 LRESULT CALLBACK WindowProcB(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {switch (uMsg) {case WM_DESTROY:PostQuitMessage(0);break;default:ret…

進行性核上性麻痹:飲食調理為健康護航

進行性核上性麻痹是一種復雜的神經退行性疾病&#xff0c;目前雖無法根治&#xff0c;但合理的健康飲食有助于緩解癥狀、提高患者生活質量。 高蛋白質食物在患者飲食中占據重要地位。魚肉&#xff0c;尤其是富含 Omega-3 脂肪酸的三文魚、鱈魚等&#xff0c;不僅蛋白質含量豐富…

【Windows+Cursor】從0到1配置Arxiv MCP Server,實現論文自主查詢、下載、分析、綜述生成

1. 安裝UV Installation | uv powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" 將安裝路徑添加到環境變量 C:\Users\xxxxxx\.local\bin 2. git clone 代碼 git clone https://github.com/blazickjp/arxiv-mcp-server.git…

WPF 教程:給 TreeView 添加 SelectedItem 雙向綁定支持(MVVM-Friendly)

&#x1f332;WPF 教程&#xff1a;給 TreeView 添加 SelectedItem 雙向綁定支持&#xff08;MVVM-Friendly&#xff09; 在 WPF 的 MVVM 應用中&#xff0c;TreeView 是非常常見的控件&#xff0c;但它有個“頑固”的缺陷&#xff1a; ?它的 SelectedItem 不是依賴屬性&…

Linux環境下內存錯誤問題排查與修復

最近這幾天服務器總是掉線&#xff0c;要查一下服務器的問題。可以首先查看一下計算機硬件&#xff0c;這是一臺某魚上拼湊的服務器&#xff1a; sudo lshw -shortH/W path Device Class Description system NF5270M3 (To be filled by O…

函數和模式化——python

一、模塊和包 將一段代碼保存為應該擴展名為.py 的文件&#xff0c;該文件就是模塊。Python中的模塊分為三種&#xff0c;分別為&#xff1a;內置模塊、第三方模塊和自定義模塊。 內置模塊和第三方模塊又稱為庫內置模塊&#xff0c;有 python 解釋器自帶&#xff0c;不用單獨安…

windows下載安裝遠程桌面工具RealVNC-Server教程(RealVNC_E4_6_1版帶注冊碼)

文章目錄 前言一、下載安裝包二、安裝步驟三、使用VNC-Viewer客戶端遠程連接&#xff0c;輸入ip地址&#xff0c;密碼完成連接 前言 在現代工作和生活中&#xff0c;遠程控制軟件為我們帶來了極大的便利。RealVNC - Server 是一款功能強大的遠程控制服務器軟件&#xff0c;通過…

Android Dagger 2 框架的注解模塊深入剖析 (一)

本人掘金號&#xff0c;歡迎點擊關注&#xff1a;https://juejin.cn/user/4406498335701950 一、引言 在 Android 開發中&#xff0c;依賴注入&#xff08;Dependency Injection&#xff0c;簡稱 DI&#xff09;是一種強大的設計模式&#xff0c;它能夠有效降低代碼的耦合度&…