Home [閱讀筆記][Design Pattern] Ch18.備忘錄模式(Memento)
Post
Cancel

[閱讀筆記][Design Pattern] Ch18.備忘錄模式(Memento)

備忘錄模式(Memento)

備忘錄模式(Memento),在不破壞封裝性的前提下,捕獲一個物件的內部狀態,並在該物件之外保存這個狀態。這樣以後就可將該物件恢復到原先保存的狀態。

使用場合?

備忘錄模式(Memento)比較適用於功能比較複雜的,但需要維護或記錄屬性歷史的類別,或者需要保存的屬性只是眾多屬性中的一小部分時,Originator可以根據保存的Memento資訊還原到前一狀態。

比如,遊戲角色類別,用來儲存角色的生命力、攻擊力、防禦力的資料。

當角色的狀態改變時,如果這個狀態無效,這時就可以使用暫時儲存起來的備忘錄將狀態復原。

結構

  • Originator發起人:負責建立一個備忘錄Memento,用以記錄當前前時刻它的內部狀態,並可以使用備忘錄恢復內部狀態。Originator可根據需要決定Memento儲存Originator的哪些內部狀態。
  • Memento備忘錄:負責儲存Originator物件的內部狀態,並可防止Originator以外的其他物件存取Memento備忘錄。備忘錄有兩個介面,Caretaker只能看到備忘錄的窄介面,它只能將備忘錄傳遞給其他物件。Originator能夠看到一個寬介面,允許它存取恢復到先前狀態所需的所有資料。

把要保存的節給封裝在Memento中,哪一天要更改保存的細節也不用影響用戶端了。

  • Caretaker管理者:負責保存好Memento備忘錄,不能對備忘錄的內容進行操作或檢查。
1
2
3
4
5
6
7
8
9
10
Originator 發起人:負責建立一個備忘錄Memento,用以記錄當前前時刻它的內部狀態,並可以使用備忘錄恢復內部狀態
+ State
+ SetMemento(in m:Memento)
+ CreateMemento()

Memento 備忘錄:負責儲存 Originator 物件的內部狀態,並可防止Originator以外的其他物件存取Memento備忘錄
+ State

Caretaker 管理者:負責保存好備忘錄 Memento
- Memento: Memento

程式碼

「遊戲角色」類別其實就是一個Originator

Originator發起人類別

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//發起人類別
class Originator {
    //需要保存的屬性,可以有很多個
    public string State { get; set; }

    //建立備忘錄
    public Memento CreateMemento() {
        //將需要保存的資訊導入並實體化出一個 Memonto物件
        return new Memento(State);
    }

    //恢復備忘錄,將Memento導入並將相關資料恢復
    public void SetMemento(Memento memento) {
        this.State = memento.state;
    }

    //顯示資料
    public void Show() {
        Console.WriteLine($"State = {this.State}");
    }
}

Memento 備忘錄類別

1
2
3
4
5
6
7
8
9
10
//備忘錄類別
class Memento {
    //需要保存的資料屬性
    public string State { get; set; }

    //建構函式,將相關資料導入
    public Memento(string state ) {
        this.State = state;
    }
}

Caretaker 管理者類別

1
2
3
4
5
//管理者類別
class Caretaker {
    //備忘錄
    public Memento Memento { get; set; }
}

用戶端程式碼

1
2
3
4
5
6
7
8
9
10
11
12
//Originator的初始狀態為ON
Originator o  = new Originator();
o.State = "on";
o.Show();

//保存狀態,由於有了很好的封裝,可以隱藏Originator的實現的細節
Caretaker c = new Caretaker();
c.Memento = o.CreateMemento();

//恢復原初始狀態
o.SetMemento(c.Memento);
o.Show();

缺點

如果狀態資料很大很多,那麼在資源消耗上會非常耗記憶體。

v1.0 儲存遊戲進度

這樣的寫法,整個遊戲角色的細節暴露給了用戶端,用戶端的職責就太大了,需要知道遊戲角色的生命力、攻擊力、防禦力這些細節,還要對它進行「備份」。以後需要增加新的資料,這部分就一定要修改了,例如:增加「魔法力」或修改現有的某種力、「生命力」要改為「經驗值」等等。

遊戲角色類別

遊戲角色類別,用來儲存角色的生命力、攻擊力、防禦力的資料

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//遊戲角色類別,用來儲存角色的生命力、攻擊力、防禦力的資料
class GameRole {   
    public int Vitality { get; set; } //生命力
    public int Attack { get; set;} //攻擊力
    public int Defense { get; set;} //防禦力

    //狀態顯示
    public void StateDisplay() {
        Console.WriteLine("角色當前狀態:");
        Console.WriteLine($"生命力: {Vitality}");
        Console.WriteLine($"攻擊力: {Attack}");
        Console.WriteLine($"防禦力: {Defense}");
    }

    //獲得初始狀態
    public void GetInit() {
        //資料通常來自本機硬碟或遠端資料庫
        Vitality = 100;
        Attack = 100;
        Defense = 100;
    }

    //戰鬥
    public void Fight() {
        //在與魔頭戰後,遊戲數據損耗為0
        Vitality = 0;
        Attack = 0;
        Defense = 0;
    }
}

用戶端調用

  • 暴露實現細節。
  • 用戶端的職責太大。

TODO: 把「遊戲角色」的存取狀態細節封裝起來,而且最好封裝在外部的類別當中,以體現職責分離。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//大戰魔頭前
GameRole o = new GameRole();
o.GetInit(); //獲得初始角色狀態
o.StateDisplay();

//保存進度 -->暴露實現細節
GameRole roleBackup = new GameRole(); //透過新實體來保存進度
roleBackup.Vitality = o.Vitality;
roleBackup.Attack = o.Attack;
roleBackup.Defense = o.Defense;

//大戰魔頭時,損耗嚴重
o.Fight(); //戰鬥後損耗嚴重數據為0
o.StateDisplay();

//恢復之前進度,重新再玩 -->暴露實現細節
o.Vitality = roleBackup.Vitality;
o.Attack = roleBackup.Attack;
o.Defense = roleBackup.Defense;
o.StateDisplay();

/* 執行結果
角色當前狀態:
生命力: 100
攻擊力: 100
防禦力: 100

角色當前狀態:
生命力: 0
攻擊力: 0
防禦力: 0

角色當前狀態:
生命力: 100
攻擊力: 100
防禦力: 100
*/

v2.0 儲存遊戲進度

結構

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
遊戲角色
+ 生命力: int
+ 攻擊力: int
+ 防禦力: int
+ 狀態查看()
+ 儲存角色狀態(): 角色狀態儲存箱
+ 恢復角色狀態(in memento: 角色狀態儲存箱)

角色狀態儲存箱
+ 生命力: int
+ 攻擊力: int
+ 防禦力: int

角色狀態管理者
+ memento: 角色狀態儲存箱

程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
//遊戲角色
class Role {
    public int Vitality { get; set; } //生命力
    public int Attack { get; set;} //攻擊力
    public int Defense { get; set;} //防禦力

    //狀態查看
    public void StateDisplay() {
        Console.WriteLine("角色當前狀態:");
        Console.WriteLine($"生命力: {Vitality}");
        Console.WriteLine($"攻擊力: {Attack}");
        Console.WriteLine($"防禦力: {Defense}");
    }

    //儲存角色狀態
    public RoleStateMemento SaveState() {
        //將遊戲角色的三個狀態值透過實體化「角色狀態儲存箱」回傳
        return new RoleStateMemento(Vitality, Attack, Defense);
    }

    //恢復角色狀態
    public void RecoveryState(RoleStateMemento memento) {
        //將外部的「角色狀態儲存箱」中的狀態值賦值給遊戲角色
        this.Vitality = memento.Vitality;
        this.Attack = memento.Attack;
        this.Defense = memento.Defense;
    }
    //獲得初始狀態
    public void GetInit() {
        //資料通常來自本機硬碟或遠端資料庫
        Vitality = 100;
        Attack = 100;
        Defense = 100;
    }
    //戰鬥
    public void Fight() {
        //在與魔頭戰後,遊戲數據損耗為0
        Vitality = 0;
        Attack = 0;
        Defense = 0;
    }
}

//角色狀態儲存箱
class RoleStateMemento {
    public int Vitality { get; set; } //生命力
    public int Attack { get; set;} //攻擊力
    public int Defense { get; set;} //防禦力

    public RoleStateMemento(int vitality, int attack, int defense) {
        this.Vitality = vitality;
        this.Attack = attack;
        this.Defense = defense;
    }
}

//角色狀態管理者
class RoleCaretaker {
    //角色狀態儲存箱
    public RoleStateMemento Memento {get; set;}
}

//用戶端程式碼
public static void Main()
{
    //大戰魔頭前
    Role role = new Role();
    role.GetInit(); //獲得初始角色狀態
    role.StateDisplay();

    //保存進度
    //保存進度時,由於封裝在Memento中,因此我們並不知道保存了哪些具體的角色資料
    RoleCaretaker stateAdmin = new RoleCaretaker();
    stateAdmin.Memento = role.SaveState();

    //大戰魔頭時,損耗嚴重
    role.Fight();
    role.StateDisplay();

    //恢復之前進度,重新再玩
    role.RecoveryState(stateAdmin.Memento);
    role.StateDisplay();
}
/* 執行結果
角色當前狀態:
生命力: 100
攻擊力: 100
防禦力: 100
角色當前狀態:
生命力: 0
攻擊力: 0
防禦力: 0
角色當前狀態:
生命力: 100
攻擊力: 100
防禦力: 100
*/
This post is licensed under CC BY 4.0 by the author.

[閱讀筆記][Design Pattern] Ch17.轉接器模式(Adapter)

[閱讀筆記][Design Pattern] Ch19.組合模式(Composite)