一、基礎概念
????????備忘錄模式的本質是【保存和恢復內部狀態】。
序號 | 備忘錄模式的思考 | 說明 |
1 | 保存是手段,恢復才是目的 | 標準的備忘錄模式保存數據的手段是通過內存緩存;廣義的備忘錄模式實現的時候,可以采用離線存儲的方式,把這些數據保存到文件或者數據庫等地方 |
2 | 備忘錄模式備忘些什么內容呢? | 備忘的就是原發器對象的內部狀態,這些內部狀態是不對外的,只有原發器對象才能夠進行操作 |
3 | 備忘錄模式為什么要保存數據呢? | 目的是為了在有需要的時候,恢復原發器對象的內部狀態,所以恢復才是備忘錄模式的目的 |
1-對于備忘錄模式最主要的一個特點【封裝狀態的備忘錄對象】不應該被除了原發器對象之外的對象訪問,至于如何存儲都是小事。 2-備忘???????錄模式要解決的主要問題是【在不破壞對象封裝性的前提下,來保存和恢復對象的內部狀態】這是一個很主要的判斷依據。 |
????????備忘錄模式的定義:在不破壞封裝性的前提下,捕獲一個對象的內部狀態,并在該對象之外保存這個狀態。這樣以后就可以將該對象恢復到原先保存的狀態了。
序號 | 認識備忘錄模式 | 說明 |
1 | 備忘錄模式的功能 | 在不破壞封裝性的前提下,捕獲一個對象的內部狀態,這里有兩點要注意: 《1》一個是不破壞封裝性(即:對象不能暴露它不應該暴露的細節); 《2》另一個是捕獲的對象的內部狀態(且通常還是運行期間某個時候對象的內部狀態) 為什么要捕獲這個對象內部狀態呢?捕獲這個內部狀態有什么用? 《1》是為了在以后的某個時候,將該對象的狀態恢復到備忘錄所保存的狀態,這才是備忘錄真正的目的; 捕獲的狀態存放在哪里呢? ????????在備忘錄模式中,捕獲的內部狀態存儲在備忘錄對象中;而備忘錄對象通常會被存儲在原發器對象之外(即:被保存狀態對象的外部)通常是放在管理者對象那里。 |
2 | 備忘錄對象 | 就是用來記錄原發起需要保存的狀態對象(簡單點的實現就是封裝數據的對象) ?備忘錄對象和普通的封裝數據對象是有區別的: 《1》備忘錄對象一般只讓原發器對象來操作(為了保證這一點,通常備忘錄對象作為原發器對象的內部類來實現,且實現為私有,這樣就斷絕了外部來訪問這個備忘錄對象的途徑);備忘錄對象需要保存在原發器對象之外,為了與外部交互,通常備忘錄對象都會實現一個窄接口來標識對象類型。 《2》普通封裝的數據對象是誰都可以使用; |
3 | 原發器對象 | 就是需要被保存狀態的對象,也有可能需要恢復狀態的對象(原發器對象一般會包含備忘錄對象的實現)。 ? ? ? ? 《1》通常原發器對象應該提供捕獲某個時刻對象內部狀態的方法,在這個方法中,原發器對象會創建備忘錄對象,把需要保存的狀態數據設置到備忘錄對象中,然后把備忘錄對象提供給管理者對象來保存; ? ? ? ? 《2》當然,原發器對象也應該提供這樣的方法(按照外部要求來恢復內部狀態到某個備忘錄對象記錄的狀態)。 |
4 | 管理者對象 | 主要是負責保存備忘錄對象: 《1》并不一定要特別的做出一個管理者對象來(廣義地說,調用原發器獲得備忘錄對象后,備忘錄對象放在哪里,哪個對象就可以算是管理者對象); 《2》管理者對象并不是只能管理一個備忘錄對象,一個管理者對象可以管理很多的備忘錄對象。 《3》狹義的管理者對象是只管理同一類的備忘錄對象,但廣義的管理者對象是可以管理不同類型的備忘錄對象的。 《4》管理者對象需要實現的基本功能主要是:存入備忘錄對象、保存備忘錄對象和獲取備忘錄對象(從功能上看,就是一個緩存功能的實現,或者是一個簡單的對象實例池的實現)。 《5》管理者雖然能存取備忘錄對象,但是不能訪問備忘錄對象內部的數據 |
5 | 窄接口和寬接口 | 在備忘錄模式中,為了控制備忘錄對象的訪問,出現了窄接口和寬接口的概念: 《1》窄接口:管理者只能看到備忘錄的窄接口,窄接口的實現中通常沒有任何的方法,只是一個類型標識。窄接口使得管理者只能將備忘錄傳遞給其他對象。 《2》寬接口:原發器能夠看到一個寬接口,允許它訪問所需的所有數據,來返回到先前的狀態(理想情況是:只允許生成備忘錄的原發器來訪問該備忘錄的內部狀態,通常實現成為原發器內的一個私有內部類) 即:我們的備忘錄模式一示例中:定義了【IFlowAMockMemo】接口,里面沒有定義任何方法,然后讓備忘錄來實現這個接口,從而標識備忘錄就是這么一個IFlowAMockMemo的類型,這個接口就是窄接口;備忘錄對象是實現在原發器內的一個私有內部類,只有原發器對象可以訪問它,原發器可以訪問到備忘錄對象中所有的內部狀態,這就是寬接口。 這是備忘錄模式的標準實現方式(即:窄接口沒有任何方法,把備忘錄對象實現成為原發器對象的私有內部類)? ? ? ? ? ? ? ? ? 能否在窄接口中提供備忘錄對象對外的方法?變相提供一個寬點的接口呢? ????????通常情況是不會這么做的(因為這樣一來,所有能拿到這個接口的對象就可以通過這個接口來訪問備忘錄內部的數據或功能,這違反了備忘錄模式的初衷【備忘錄模式要求“在不破壞封裝性”的前提下】如果這么做,那就等于是暴露了內部細節。因此,備忘錄模式在實現的時候,對外多是采用窄接口,而且通常不會定義任何方法)。 ? ? ?? |
6 | 使用備忘錄模式 潛在的代價 | 標準的備忘錄模式的實現機制是依靠緩存來實現的,因此,當需要備忘錄的數據量較大時,或者是存儲的備忘錄對象數據量不大但是數量很多的時候,或者是用戶很頻繁地創建備忘錄對象時,這些都會導致非常大的內存開銷。因此在使用備忘錄模式的時候,一定要好好思考應用的環境,如果使用的代價太高,就不要選擇備忘錄模式,可以采用其他替代方案。 |
7 | 增量存儲 | 如果需要頻繁地創建備忘錄對象,而且創建和應用備忘錄對象來恢復狀態的順序是可控的,那么可以讓備忘錄進行增量存儲,也就是備忘錄可以僅僅存儲原發器內部相對于上一次存儲狀態后的增量改變。 (如:在命令模式實現可撤銷命令的實現中,就可以使用備忘錄來保存每個命令對象的狀態,然后在撤銷命令的時候,使用備忘錄來恢復這些狀態。由于命令的歷史列表是按照命令操作的順序來存放的,也是按照這個歷史列表來進行取消和重做的,因此順序是可控的, 那么這種情況,還可以讓備忘錄對象只存儲一個命令所產生的增量改變而不是它所影響的每一個對象的完整狀態)。 |
序號 | 備忘錄模式的優點 | 備忘錄模式的缺點 |
1 | 更好的封裝性 ????????通過使用備忘錄對象,來封裝原發器對象的內部狀態,雖然這個對象是保存在原發器對象的外部,但是由于備忘錄對象的窄接口并不提供任何方法,這樣有效保證了對原發器對象內部狀態的封裝,不把原發器對象的內部實現細節暴露給外部。 | 可能會導致高開銷 ????????備忘錄模式基本的功能就是備忘錄對象的存儲和恢復,它的基本實現方式就是緩存備忘錄對象。這樣一來,如果需要緩存的數量很大,或者是特別頻繁地創建備忘錄對象,開銷是很大的。 |
2 | 簡化了原發器 ????????備忘錄對象被保存到原發器對象之外,讓客戶來管理他們的請求狀態,從而讓原發器對象得到簡化。 | |
3 | 窄接口和寬接口 ????????引入窄接口和寬接口,使得不同的地方,對備忘錄對象的訪問是不一樣的。窄接口保證了只有原發器才可以訪問備忘錄對象的狀態。 |
????????何時選用備忘錄模式?
????????《1》如果必須保存一個對象在某一個時刻的全部或者部分狀態,方便在以后需要的時候,可以把該對象恢復到先前的狀態,可以使用備忘錄模式【使用備忘錄對象來封裝和保存需要保存的內部狀態,然后把備忘錄對象保存到管理者對象中,在需要的時候,再從管理者對象中獲取備忘錄對象,來恢復對象的狀態】。
????????《2》如果需要保存一個對象的內部狀態,但是如果用接口來讓其他對象直接得到這些需要保存的狀態,將會暴露對象的實現細節并破壞對象的封裝性,這時可以使用備忘錄模式【把備忘錄對象實現細節成為原發器對象的內部類,而且還是私有,從而保證了只有原發器對象才能訪問該備忘錄對象;這樣即保存了需要保存的狀態,又不會暴露內部實現細節】。
二、備忘錄模式示例
????????業務需求:現在有一個仿真應用(功能是:模擬運行針對某個問題的多個解決方案,記錄運行過程中的各種數據,在模擬運行完成之后,方便對這個多個解決方案進行比較和評價,從而選定最優的解決方案)。這種仿真系統,在很多領域都有應用(如:工作流系統,對同一個問題制定多個流程,然后通過仿真運行,最后來確定最優的流程作為解決方案;在工業設計和制造領域,仿真系統的應用就更加廣泛了)。
????????由于都是解決同一個具體的問題,這多個解決方案并不是完全一樣的,現在我們假定它們的前半部分運行是完全一樣的,只是在后半部分采用了不同的解決方案,后半部分需要使用前半部分運行所產生的數據。
?2.1、不使用模式的示例
要保證初始數據一致,實現思路是:
《1》模擬運行流程的第一個階段,得到后階段各個方案運行所需的數據,并把數據保存下來,方便之后的各個方案使用。
《2》每次在模擬運行某一個方案之前,用保存的數據去重新設置模擬運行流程的對象,這樣在運行后面不同的方案時,對于這些方案來說,初始的數據就都是一樣的了。
? 2.1.1、編寫模擬運行流程A
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace MemoPattern.NoPattern
{/// <summary>/// 模擬運行流程A(這是這是一個示意,用于指代具體流程)/// </summary>internal class FlowAMock{//流程名稱(不用額外存儲的狀態數據)public string FlowName { get; set; }=string.Empty;//示意:代指某個中間的結果(需要外部存儲的狀態數據)public int TempResult { get; set; } = 0;//示意:代指某個中間結果(需要外部存儲的狀態數據)public string TempState { get; set; } = string.Empty;/// <summary>/// 構造函數/// </summary>/// <param name="flowName">流程名稱</param>public FlowAMock(string flowName){this.FlowName = flowName;}/// <summary>/// 示意:運行流程的第一個階段/// </summary>public void RunPhaseOne(){//在這個階段可能產生了中間結果,這里僅作示意TempResult = 3;TempState = "PhaseOne";}/// <summary>/// 示意:按照方案一運行流程后半部分/// </summary>public void SchemaOne(){//示意:需要使用第一階段產生的數據this.TempState += "[SchemaOne]";Console.WriteLine($"方案一目前的狀態是【{TempState}】結果是【{TempResult}】");TempResult += 11;}/// <summary>/// 示意:按照方案二運行流程后半部分/// </summary>public void SchemaTwo(){//示意:需要使用第一階段產生的數據this.TempState += "[SchemaTwo]";Console.WriteLine($"方案二目前的狀態是【{TempState}】結果是【{TempResult}】");TempResult += 22;}}//Class_end
}
? 2.1.2、客戶端測試
namespace MemoPattern
{internal class Program{static void Main(string[] args){TestFlowAMock();Console.ReadLine();}/// <summary>/// 測試模擬運行流程/// </summary>private static void TestFlowAMock(){Console.WriteLine("------測試模擬運行流程------");NoPattern.FlowAMock flowAMock = new NoPattern.FlowAMock("TestFlow");//運行流程的第一階段flowAMock.RunPhaseOne();//獲取到運行流程第一階段后產生的數據,提供給后續使用【可確保后續流程的方案一、方案二使用的基礎數據是一樣的】int tempResult = flowAMock.TempResult;string tempState=flowAMock.TempState;//按照方案一運行流程后半部分flowAMock.SchemaOne();//運行完成方案一后將數據重設回去flowAMock.TempResult = tempResult;flowAMock.TempState = tempState;//按照方案二運行流程后半部分flowAMock.SchemaTwo();}}//Class_end
}
? 2.1.3、運行結果
?這個不使用模式的方式是滿足了業務的需求【即:在運行第一個階段后,方案一和方案二運行時的初始數據是一樣的】。但是這個實現有兩個問題:
????????《1》數據都是零散存放在外部,若需要存放的外部數據很多,就會很雜亂(可定義一個對象來封裝);
????????《2》還有一個嚴重問題是:運行期間的數據都放到外部存儲起來了,模擬流程的對象被迫將內部數據結構開放給外部,這暴露了對象的實現細節,且破壞了對象的封裝性【正常來說這些數據只是模擬流程對象內部的數據,不應該對外開放】。
?2.2、備忘錄模式的示例1
????????需要在運行期間捕獲模擬流程運行對象的內部狀態【即:這個需求就是運行第一個階段產生的內部數據,并且在該對象之外來保存這些狀態,因為在后面它有不同的運行方案。但是這些不同的運行方案需要的初始數據是一樣的,都是流程在第一階段運行產生的數據】。
那么如何能夠在不破壞對象封裝性的前提下,來保存和恢復對象的狀態?【
????????《1》備忘錄模式引入了一個存儲狀態的備忘錄對象,為了讓外部無法訪問這個對象的值,一般把這個對象實現為需要保存數據對象的內部類,且是私有的;這樣除了需要保存數據的對象,外部無法訪問到這個備忘錄對象的數據,就保證了對象的封裝性不被破壞。
????????《2》備忘錄對象還需要存儲在外部,為了避免讓外部訪問到這個對象內部的數據,備忘錄模式還引入了一個備忘錄對象的窄接口,這個接口一般都是空的(什么方法也沒有);這樣外部存儲的地方,只知道存儲了一些備忘錄接口的對象,但由于接口是空的,它們無法通過接口去訪問備忘錄對象內部的數據】。
如何使用備忘錄模式?
《1》模擬流程運行的對象就相當于備忘錄模式中的原發器;
《2》在模擬流程對象里面創建一個內部私有類作為備忘錄對象來存儲數據;
《3》為了和管理者對象交互,管理者需要知道保存對象的類型,就提供一個備忘錄對象的窄接口來供管理者使用。
? 2.2.1、定義窄接口提供給管理者使用
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace MemoPattern.MemoDemoOne
{/// <summary>/// 備忘錄的窄接口(沒有任何方法定義)/// </summary>internal interface IFlowAMockMemo{}//Interface_end
}
? 2.2.2、實現模擬運行流程A
????????模擬運行流程A對象相當于原發器對象(該類里面原有的內部屬性狀態內容不用再暴露出去【即:不在對外提供公有的屬性方法】;其次就是在該類中提供私有的備忘錄對象,里面封裝想要保存的內部狀態,同時讓這個備忘錄對象實現窄接口;最后就是這個類中提供創建備忘錄對象和根據備忘錄對象恢復內部狀態的方法)。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace MemoPattern.MemoDemoOne
{/// <summary>/// 模擬運行流程A(這是這是一個示意,用于指代具體流程)【備忘錄模式的原發器對象】/// </summary>internal class FlowAMock{//流程名稱(不用額外存儲的狀態數據)private string flowName = string.Empty;//示意:代指某個中間的結果(需要外部存儲的狀態數據)private int tempResult = 0;//示意:代指某個中間結果(需要外部存儲的狀態數據)private string tempState = string.Empty;/// <summary>/// 構造函數/// </summary>/// <param name="flowName">流程名稱</param>public FlowAMock(string flowName){this.flowName = flowName;}/// <summary>/// 示意:運行流程的第一個階段/// </summary>public void RunPhaseOne(){//在這個階段可能產生了中間結果,這里僅作示意tempResult = 3;tempState = "PhaseOne";}/// <summary>/// 示意:按照方案一運行流程后半部分/// </summary>public void SchemaOne(){//示意:需要使用第一階段產生的數據this.tempState += "[SchemaOne]";Console.WriteLine($"方案一目前的狀態是【{tempState}】結果是【{tempResult}】");tempResult += 11;}/// <summary>/// 示意:按照方案二運行流程后半部分/// </summary>public void SchemaTwo(){//示意:需要使用第一階段產生的數據this.tempState += "[SchemaTwo]";Console.WriteLine($"方案二目前的狀態是【{tempState}】結果是【{tempResult}】");tempResult += 22;}#region 備忘錄內容/// <summary>/// 繼承備忘錄接口實現真正的備忘錄功能(實現內部私有類,不讓外部訪問)/// </summary>private class MemoImpl : IFlowAMockMemo{//保存中間的某個結果(此處僅做示意)private int tempResult=0;//保存某個中間狀態private string tempState = string.Empty;/// <summary>/// 結果內容外部只讀/// </summary>public int TempResult { get { return tempResult; } }/// <summary>/// 狀態內容外部只讀/// </summary>public string TempState { get { return tempState; } }public MemoImpl(int tempResult,string tempState){this.tempResult = tempResult;this.tempState = tempState;}}/// <summary>/// 創建保存原發器對象的備忘錄對象/// </summary>/// <returns>返回創建好的備忘錄對象</returns>public IFlowAMockMemo CreateMemo(){return new MemoImpl(this.tempResult,this.tempState);}/// <summary>/// 重新設置原發器對象狀態,讓其回到備忘錄對象記錄的狀態/// </summary>/// <param name="flowAMockMemo"></param>public void SetMemo(IFlowAMockMemo flowAMockMemo){MemoImpl memoImpl = (MemoImpl)flowAMockMemo;this.tempResult = memoImpl.TempResult;this.tempState = memoImpl.TempState;}#endregion}//Class_end
}
? 2.2.3、實現備忘錄對象的管理者
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace MemoPattern.MemoDemoOne
{/// <summary>/// 負責保存模擬運行流程A對象的備忘錄管理者對象/// </summary>internal class FlowAMemoManager{//記錄保存的備忘錄對象private IFlowAMockMemo flowAMockMemo = null;/// <summary>/// 保存備忘錄對象/// </summary>/// <param name="flowAMockMemo"></param>public void SaveMemo(IFlowAMockMemo flowAMockMemo){this.flowAMockMemo = flowAMockMemo;}/// <summary>/// 獲取被保存的備忘錄對象/// </summary>/// <returns></returns>public IFlowAMockMemo RetriveMemo(){return this.flowAMockMemo;}}//Class_end
}
? 2.2.4、客戶端測試
namespace MemoPattern
{internal class Program{static void Main(string[] args){TestMemoDemoOne();Console.ReadLine();}/// <summary>/// 測試備忘錄示例一/// </summary>private static void TestMemoDemoOne(){Console.WriteLine("------測試備忘錄示例一------");//創建模擬運行的流程對象MemoDemoOne.FlowAMock flowAMock = new MemoDemoOne.FlowAMock("TestFlow");//運行流程的第一個階段flowAMock.RunPhaseOne();/*創建需運行對象的管理者維護該對象的數據某個時段的狀態*///創建一個流程的管理者MemoDemoOne.FlowAMemoManager flowAMemoManager = new MemoDemoOne.FlowAMemoManager();//創建【此模擬運行流程對象】的備忘錄對象(并保存到管理者對象里面提供給后續使用)MemoDemoOne.IFlowAMockMemo flowAMockMemo = flowAMock.CreateMemo();flowAMemoManager.SaveMemo(flowAMockMemo);//按照方案一運行流程的后半部分flowAMock.SchemaOne();//從備忘錄管理者那里獲取備忘錄對象,并設置回去(即:實現模擬運行流程對象自己恢復到內部狀態)flowAMock.SetMemo(flowAMemoManager.RetriveMemo());//按照方案二運行流程的后半部分flowAMock.SchemaTwo();}}//Class_end
}
? 2.2.5、運行結果
????????由于備忘錄對象是一個私有的內部類,外面只能通過備忘錄對象的窄接口來獲取備忘錄對象,而這個接口沒有任何方法,僅僅起到了一個標識對象類型的作用,從而保證了內部數據不會被外部獲取或是操作,保證了原發器對象的封裝性,也就不會再暴露原發器對象的內部結構了。
?2.3、備忘錄模式的示例2
????????業務需求:考慮一個計算器的功能(最簡單的那種方式,只實現加減法運算,且現在需要對加減法實現可撤銷和恢復操作)。
在前面的命令模式中講到了可撤銷的操作;當時說過關于實現撤銷恢復操作有兩種思路:
《1》補償式(或叫反操作式)【即:若被撤銷的功能是加功能,那撤銷的實現就變為減,以此類推】。
《2》存儲恢復式【即:把操作前的狀態記錄下來,然后要撤銷操作的時候直接恢復回去就可以了】。我們這里就采用存儲恢復式的方法來實現。
? 2.3.1、定義備忘錄對象的窄接口
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace MemoPattern.MemoDemoTwo
{/// <summary>/// 定義備忘錄對象的窄接口【不定義任何方法】/// </summary>internal interface IMemo{}//Interface_end
}
? 2.3.2、定義命令接口
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace MemoPattern.MemoDemoTwo
{/// <summary>/// 定義命令接口/// </summary>internal interface ICommand{//執行命令void Execute();//撤銷命令(恢復到備忘錄對象記錄的狀態)void Undo(IMemo memo);//重做命令(恢復到備忘錄對象記錄的狀態)void Redo(IMemo memo);//創建保存原觸發器對象狀態的備忘錄對象IMemo CreateMemo();}//Interface_end
}
? 2.3.3、定義操作運算的接口
這個操作運算的接口相當于計算器類的原發器對外提供的接口:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace MemoPattern.MemoDemoTwo
{/// <summary>/// 操作運算的接口/// </summary>internal interface IOperation{//獲取計算完成后的結果int GetResult();//執行加法void Add(int num);//執行減法void Substract(int num);//創建保存原發器對象狀態的備忘錄對象IMemo CreateMemo();//重新設置原發器對象狀態,讓其回到備忘錄對象記錄的狀態void SetMemo(IMemo memo);}//Interface_end
}
? 2.3.4、實現公共的命令對象
????????由于現在執行撤銷和恢復操作都是通過使用備忘錄對象來恢復原發器的狀態,因此就不用按照操作類型來區分,對于所有的命令實現,無論是加法還是減法它們的撤銷和重做都是一樣的。因此我,們這里實現一個所有命令的公共對象,實現公共功能,這個在實現具體的命令時就簡單了。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace MemoPattern.MemoDemoTwo
{/// <summary>/// 命令對象的公共對象,實現各個命令對象的公共方法/// </summary>abstract internal class AbstractCommand : ICommand{//持有真正的命令實現對象protected IOperation operation = null;public void SetOperation(IOperation operation){this.operation=operation;}public IMemo CreateMemo(){return this.operation.CreateMemo();}public virtual void Execute(){//具體的功能實現,抽象類這里就不提供默認實現,讓具體類自己實現throw new NotImplementedException();}public void Redo(IMemo memo){this.operation.SetMemo(memo);}public void Undo(IMemo memo){this.operation.SetMemo(memo);}}//Class_end
}
? 2.3.5、實現具體的命令對象
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace MemoPattern.MemoDemoTwo
{/// <summary>/// 具體的添加命令對象/// </summary>internal class AddCommand:AbstractCommand{private int operationNum = 0;public AddCommand(int operationNum){this.operationNum = operationNum;}public override void Execute(){Console.WriteLine($"準備添加數字【{operationNum}】");this.operation.Add(operationNum);}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace MemoPattern.MemoDemoTwo
{/// <summary>/// 具體的減法命令對象/// </summary>internal class SubstractCommand:AbstractCommand{private int operationNum = 0;public SubstractCommand(int operationNum){this.operationNum = operationNum;}public override void Execute(){Console.WriteLine($"準備減去數字【{operationNum}】");this.operation.Substract(operationNum);}}//Class_end
}
? 2.3.6、實現運算類
????????這個運算類實現的是真正的運算邏輯內容,在這里創建操作備忘錄對象【也就是說該類就是原發器對象】。該類不在對外提供屬性的訪問方法,只允許內部操作,外部無法操作。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace MemoPattern.MemoDemoTwo
{/// <summary>/// 具體的運算類,真正實現加減法運算/// </summary>internal class Operation : IOperation{//記錄運算結果private int result = 0;public void Add(int num){result += num;}public IMemo CreateMemo(){MemoImpl memoImpl = new MemoImpl(result);return memoImpl;}public int GetResult(){return result;}public void SetMemo(IMemo memo){MemoImpl memoImpl = (MemoImpl)memo;this.result = memoImpl.GetResult();}public void Substract(int num){result -= num;}#region 備忘錄對象實現private class MemoImpl : IMemo{private int result = 0;public MemoImpl(int result){this.result = result;}public int GetResult(){return result;}}#endregion}//Class_end
}
? 2.3.7、實現計算器類
????????這個計算器類相當于備忘錄模式的管理者對象,在這個類里面實現撤銷和重做操作,還有對應的加法、減法按鈕功能實現。由于每個命令對象的撤銷和重做狀態是不一樣的,撤銷是回到命令操作前的狀態;而重做是回到命令操作后的狀態,因此對每一個命令,使用一個備忘錄對象的數組來記錄對應的狀態。這些備忘錄對象和命令對象是相對應的,因此也跟命令歷史記錄一樣,設置相應的歷史記錄,它的順序和命令完全對應起來。在操作命令歷史記錄的同事,對應操作相應的備忘錄對象記錄。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace MemoPattern.MemoDemoTwo
{/// <summary>/// 計算器對象(計算器上的加法、減法、撤銷、恢復按鈕)【相當于備忘錄對象的管理者】/// </summary>internal class Calculator{//命令操作的歷史記錄(在撤銷時使用)private List<ICommand> undoCmdList=new List<ICommand>();//命令被撤銷的歷史記錄(在恢復時使用)private List<ICommand> redoCmdList=new List<ICommand>();//命令操作對應的備忘錄對象的歷史記錄,在撤銷時使用//(數組有兩個元素,第一個是命令執前的狀態;第二個是命令執行后的狀態)private List<IMemo[]>undoMemoList=new List<IMemo[]>();//被撤銷命令對應的備忘錄對象的歷史記錄,在恢復時使用//(數組有兩個元素,第一個是命令執行前的狀態,第二個是命令執行后的狀態)private List<IMemo[]>redoMemoList=new List<IMemo[]>();//加法命令private ICommand addCmd = null;//減法命令private ICommand subCmd = null;/// <summary>/// 設置操作為加法/// </summary>/// <param name="command"></param>public void SetAddCmd(ICommand command){this.addCmd = command;}/// <summary>/// 設置操作為減法/// </summary>/// <param name="command"></param>public void SetSubCmd(ICommand command){this.subCmd = command;}/// <summary>/// 加法按鈕邏輯/// </summary>public void AddPressed(){//獲取對應的備忘錄對象,并保存在相應的歷史記錄中IMemo memo1 = this.addCmd.CreateMemo();//執行命令this.addCmd.Execute();//將操作記錄到歷史記錄中this.undoCmdList.Add(this.addCmd);//獲取執行命令后的備忘錄對象IMemo memo2 = this.addCmd.CreateMemo();//設置到撤銷歷史記錄中this.undoMemoList.Add(new IMemo[] { memo1,memo2});}/// <summary>/// 減法按鈕邏輯/// </summary>public void SubPressed(){//獲取對應的備忘錄對象,并保存到相應的歷史記錄中IMemo memo1=this.subCmd.CreateMemo();//執行命令this.subCmd.Execute();//把操作記錄到歷史記錄中undoCmdList.Add(this.subCmd);//獲取執行命令后的備忘錄對象IMemo memo2=this.subCmd.CreateMemo();//設置到撤銷的歷史記錄中this.undoMemoList.Add(new IMemo[] {memo1,memo2 });}/// <summary>/// 撤銷按鈕/// </summary>public void UndoPressed(){if (undoCmdList.Count > 0){//取出最后一個命令來撤銷ICommand cmd = undoCmdList[undoCmdList.Count - 1];//獲取對應的備忘錄對象IMemo[] memos = undoMemoList[undoCmdList.Count - 1];//撤銷cmd.Undo(memos[0]);//如果還有恢復功能,那就把這個命令記錄到恢復的歷史記錄中redoCmdList.Add(cmd);//把相應的備忘錄對象也添加到恢復備忘錄列表中redoMemoList.Add(memos);//將最后的命令刪除undoCmdList.Remove(cmd);//將相應的備忘錄對象也刪除undoMemoList.Remove(memos);}else{Console.WriteLine("很抱歉,已經沒有可撤銷的命令了");}}/// <summary>/// 恢復按鈕/// </summary>public void RedoPressed(){if (redoCmdList.Count > 0){//取出最后一個命令來重做ICommand cmd = redoCmdList[redoCmdList.Count - 1];//獲取相應的備忘錄對象IMemo[] memos = redoMemoList[redoCmdList.Count - 1];//重做cmd.Redo(memos[1]);//將這個命令記錄到可撤銷的歷史記錄中undoCmdList.Add(cmd);//把相應的備忘錄對象也添加到撤銷列表中undoMemoList.Add(memos);//將最后一個命令刪除redoCmdList.Remove(cmd);//將對應的備忘錄對象也刪除redoMemoList.Remove(memos);}else{Console.WriteLine("很抱歉,已經沒有可恢復的命令了");}}}//Class_end
}
? 2.3.8、客戶端測試
namespace MemoPattern
{internal class Program{static void Main(string[] args){TestMemoDemoTwo();Console.ReadLine();}/// <summary>/// 測試備忘錄示例二/// </summary>private static void TestMemoDemoTwo(){Console.WriteLine("------測試備忘錄示例二------");/*1-組裝命令和接收者*///創建操作對象【作為命令的接收者】MemoDemoTwo.IOperation operation = new MemoDemoTwo.Operation();//創建加法命令MemoDemoTwo.AddCommand addCmd = new MemoDemoTwo.AddCommand(5);//創建減法命令MemoDemoTwo.SubstractCommand subCmd=new MemoDemoTwo.SubstractCommand(3);//組裝命令和接收者addCmd.SetOperation(operation);subCmd.SetOperation(operation);/*2-把命令設置到持有者【計算器中】*/MemoDemoTwo.Calculator calculator=new MemoDemoTwo.Calculator();calculator.SetAddCmd(addCmd);calculator.SetSubCmd(subCmd);/*3-模擬按下指定按鈕*/calculator.AddPressed();Console.WriteLine($"執行【加法】一次運算后的結果是【{operation.GetResult()}】");calculator.SubPressed();Console.WriteLine($"執行【減法】一次運算后的結果是【{operation.GetResult()}】");/*4-測試撤銷*/for ( int i = 1; i <=3 ; i++ ){calculator.UndoPressed();Console.WriteLine($"執行【撤銷】【{i}】次后的結果是【{operation.GetResult()}】");}/*5-測試恢復*/for ( int i = 1;i <=3 ; i++ ){calculator.RedoPressed();Console.WriteLine($"執行【恢復】【{i}】次后的結果是【{operation.GetResult()}】");}}}//Class_end
}
? 2.3.9、運行結果
三、項目源碼工程
kafeiweimei/Learning_DesignPattern: 這是一個關于C#語言編寫的基礎設計模式項目工程,方便學習理解常見的26種設計模式https://github.com/kafeiweimei/Learning_DesignPattern