Memento Pattern
The memo pattern is a behavioral pattern that captures the internal state of an object and stores that state outside of the object without breaking encapsulation. You can then restore the object to its original saved state.
The memo pattern provides us with a “regret medicine” mechanism that allows us to undo or even redo changes to objects when needed.
role
1. Originator
Create a memo to record its internal state at the current moment, and use the memo to restore the internal state if needed;
2. Memento
Stores the internal state of the originator object;
3, Caretaker
Responsible for keeping the memo, can not operate or check the contents of the memo.
The sample
The MementoPattern namespace contains the Memento memo class, the Caretaker manager class, and the chess Chessman class. This example will show you how to undo or redo a chess position modification. This example uses a stack to support multiple undo, and redo supports multiple undo of the previous one. This case does not support the branch caused by resetting the position of the piece.
public partial class Chessman {
private Point _position;
private Caretaker _caretaker = null;
public Point Position {
get => _position;
set {
_position = value;
_caretaker.Memento.Position = value; Console.WriteLine( String.Format(Const.POSITION_INFO, _position.X, _position.Y)); }}public Chessman() : this(new Point(0.0)){}public Chessman(Point point) {
_caretaker = new Caretaker(newMemento()); Position = point; }}Copy the code
The Chessman class maintains the position of the chess pieces internally, and saves the information in the memo managed by the manager when setting the position of the chess pieces.
public partial class Chessman {
public Chessman Undo(int step) {
try {
Console.WriteLine(Const.ARROW_LEFT);
Console.WriteLine($"Undo({step})!");
this._position = _caretaker.Memento.Undo(step);
Console.WriteLine(
String.Format(Const.POSITION_INFO, _position.X, _position.Y));
Console.WriteLine(Const.ARROW_RIGHT);
return this;
} catch(Exception ex) {
Console.WriteLine(ex.Message);
Console.WriteLine(Const.ARROW_RIGHT);
return this; }}public Chessman Redo() {
try {
Console.WriteLine(Const.ARROW_LEFT);
Console.WriteLine("Redo()!");
this._position = _caretaker.Memento.Redo();
Console.WriteLine(
String.Format(Const.POSITION_INFO, _position.X, _position.Y));
Console.WriteLine(Const.ARROW_RIGHT);
return this;
} catch(Exception ex) {
Console.WriteLine(ex.Message);
Console.WriteLine(Const.ARROW_RIGHT);
return this; }}}Copy the code
Part 2 of the Chessman class, partial, supports undo positions by step and supports redo commands.
public partial class Memento {
private Point _position;
public Point Position {
get => _position;
set {
_position = value;
_history.Push(new RedoInfo { Position = value}); _redoList.Clear(); }}public Memento() {
_history = new Stack<RedoInfo>();
_redoList = new Stack<RedoInfo>();
}
private Stack<RedoInfo> _history = null;
private Stack<RedoInfo> _redoList = null;
public Point Undo(int step) {
int totalCount = 0;
List<string> temp = new List<string> ();foreach(var item in _history) {
if(string.IsNullOrWhiteSpace(item.GUID)) {
totalCount++;
} else {
if(! temp.Contains(item.GUID)) { totalCount++; temp.Add(item.GUID); }}}if(step >= totalCount) {
throw new InvalidOperationException("Too much steps!");
}
var guid = Guid.NewGuid().ToString("B");
for(int i = 1; i <= step; i++) {
Undo(guid);
}
return_position; }}Copy the code
The Memento class maintains internal references to locations and manages historical and redo data in two stacks, respectively.
public partial class Memento {
private void UndoLoop(string guid) {
var history = _history.Pop();
history.GUID = guid;
_redoList.Push(history);
_position = _history.Peek().Position;
}
private void Undo(string guid) {
var temp = _history.Peek().GUID;
if(string.IsNullOrWhiteSpace(temp)) {
UndoLoop(guid);
} else {
while(_history.Peek().GUID == temp) { UndoLoop(guid); }}}public Point Redo() {
if(_redoList.Count == 0) {
throw new InvalidOperationException("You can not redo now!");
}
var guid = _redoList.Peek().GUID;
while(_redoList.Count ! =0 && _redoList.Peek().GUID == guid) {
_history.Push(_redoList.Pop());
_position = _history.Peek().Position;
}
return_position; }}Copy the code
Part 2 of the memo class Memento, partial, contains the specific logic for step undo and redo.
public class Caretaker {
public Memento Memento { get; set; }
public Caretaker(Memento memento){ Memento = memento; }}Copy the code
E.g. The Caretaker maintains the Caretaker.
public class RedoInfo {
public Point Position { get; set; }
public string GUID { get; set; }}Copy the code
Redo the RedoInfo class to determine if the same “batch” was revoked based on the GUID value.
public class Const {
public const string POSITION_INFO = "Current position is ({0},{1})!";
public const string ARROW_LEFT = "< -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --";
public const string ARROW_RIGHT = "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - >";
}
Copy the code
The Const class maintains some of the strings that are often used in this case. This class should not exist in the actual development process, and the corresponding constants should be placed in the specific class to be used. In 2017, Alibaba released the Alibaba Java Development Manual, which contains a section referring to this guideline, which should be followed by all developers using object-oriented programming languages.
public class Program {
private static Chessman _chessman = null;
public static void Main(string[] args) {
_chessman = new Chessman(new Point(1.10));
_chessman.Position = new Point(2.20);
_chessman.Position = new Point(3.30);
_chessman.Position = new Point(4.40);
_chessman.Position = new Point(5.50);
_chessman.Position = new Point(9.40);
_chessman.Undo(1)
.Undo(2)
.Undo(1)
.Redo()
.Redo()
.Redo()
.Redo()
.Redo()
.Undo(6)
.Undo(5)
.Undo(4); Console.ReadKey(); }}Copy the code
Above is the caller code demo, the undo and redo methods are specially processed to support the method chain. Here is the output of this case:
Current position is (1.10)!
Current position is (2.20)!
Current position is (3.30)!
Current position is (4.40)!
Current position is (5.50)!
Current position is (9.40)!
<---------------------------
Undo(1)!
Current position is (5.50)!
--------------------------->
<---------------------------
Undo(2)!
Current position is (3.30)!
--------------------------->
<---------------------------
Undo(1)!
Current position is (2.20)!
--------------------------->
<---------------------------
Redo()!
Current position is (3.30)!
--------------------------->
<---------------------------
Redo()!
Current position is (5.50)!
--------------------------->
<---------------------------
Redo()!
Current position is (9.40)!
--------------------------->
<---------------------------
Redo()!
You can not redo now!
--------------------------->
<---------------------------
Redo()!
You can not redo now!
--------------------------->
<---------------------------
Undo(6)!
Too much steps!
--------------------------->
<---------------------------
Undo(5)!
Too much steps!
--------------------------->
<---------------------------
Undo(4)!
Current position is (1.10)!
--------------------------->
Copy the code
advantages
1. It provides a mechanism for the user to restore the state, enabling the user to easily return to a historical state; 2. Information encapsulation is realized, so that users do not need to care about the details of state preservation.
disadvantages
1. If a class has too many member variables, it is bound to take up a lot of resources, and each save will consume a certain amount of memory.
Usage scenarios
1. State scenarios where data needs to be saved/restored; 2. Provide a rollback operation.