我的 WinClock 项目系列之四 (Memento 模式的应用)
动机 (Motivation)
在软件的构建过程中,某些对象的状态在转换过程中,可能由于某种需要,
要求程序能够回溯到对象之前某个点时的状态,如果使用一些公有接口来让
其他对象得到对象的状态,便会暴露对象的细节实现。
如何实现对象状态的良好保存与恢复?但同时又不会因此而破坏对象本身的
封装性。
意图 (Intent)
在不破坏封装性的前提下,捕获一个对象的内部状态,并在这个对象之外保存
这个状态。这样就可以将对象恢复到原先保存的状态。
《设计模式》———— GOF
从类图可以看出, ClockOption, MainForm, RemindData, RemindOparate 都实现了
一个 IMementoCapable 接口,这个接口定义如下:
很简单,只是能够把一个对象的状态保存为一个Properties对象,或者利用一个Properties
对象把这个对象的状态恢复到之前的状态。
这样做是很有必要的,比如对于 OptionForm,是需要可以预览的,这样用户在更改了一个
设置后马上就可以看到效果。比如拖动 Size 滑块,马上可以看到变大或者变小的效果。这样
的功能用到的还不少,使用 Memento 在窗体加载前先保存好原有的状态,在取消后就可以很
方便到恢复到之前的状态。下面是 Option 菜单执行的操作:
分钟检测一次是否有提醒任务。
属性的。主要是通过Get<T>,Set<T>两个泛型方法:
李建忠 C#面向对象设计模式纵横谈(21):(行为型模式) Memento 备忘录模式
SharpDevelop 3.0 源代码
在软件的构建过程中,某些对象的状态在转换过程中,可能由于某种需要,
要求程序能够回溯到对象之前某个点时的状态,如果使用一些公有接口来让
其他对象得到对象的状态,便会暴露对象的细节实现。
如何实现对象状态的良好保存与恢复?但同时又不会因此而破坏对象本身的
封装性。
意图 (Intent)
在不破坏封装性的前提下,捕获一个对象的内部状态,并在这个对象之外保存
这个状态。这样就可以将对象恢复到原先保存的状态。
《设计模式》———— GOF
从类图可以看出, ClockOption, MainForm, RemindData, RemindOparate 都实现了
一个 IMementoCapable 接口,这个接口定义如下:
1
// IMementoCapable.cs
2
public interface IMementoCapable {
3
Properties CreateMemento();
4
void SetMemento(Properties properties);
5
}
// IMementoCapable.cs2
public interface IMementoCapable {3
Properties CreateMemento();4
void SetMemento(Properties properties);5
}很简单,只是能够把一个对象的状态保存为一个Properties对象,或者利用一个Properties
对象把这个对象的状态恢复到之前的状态。
这样做是很有必要的,比如对于 OptionForm,是需要可以预览的,这样用户在更改了一个
设置后马上就可以看到效果。比如拖动 Size 滑块,马上可以看到变大或者变小的效果。这样
的功能用到的还不少,使用 Memento 在窗体加载前先保存好原有的状态,在取消后就可以很
方便到恢复到之前的状态。下面是 Option 菜单执行的操作:
1
// OptionElement.cs
2
internal class OptionElement : Element {
3
public OptionElement(Mediator mediator, ToolStripMenuItem source)
4
: base(mediator) {
5
base.command = new MenuItemCommand(source);
6
}
7
8
protected override void OnExecute() {
9
using (OptionForm optionForm = new OptionForm(base.mainForm)) {
10
Core.Properties memento = mainForm.CreateMemento();
11
byte previewOpacity = clockOpt.PreviewOpacity;
12
if (optionForm.ShowDialog() == DialogResult.Cancel) {
13
mainForm.SetMemento(memento);
14
base.clockOpt.PreviewOpacity = previewOpacity;
15
mainForm.RefreshSkin();
16
} else { // OK clicked
17
clockOpt.PreviewOpacity = clockOpt.Opacity;
18
Point location = mainForm.Location;
19
mainForm.CheckBounds(ref location);
20
mainForm.Location = location;
21
mainForm.RefreshWindow();
22
}
23
}
24
}
25
}
下面是 Remind 菜单要执行的操作:
// OptionElement.cs2
internal class OptionElement : Element {3
public OptionElement(Mediator mediator, ToolStripMenuItem source)4
: base(mediator) {5
base.command = new MenuItemCommand(source);6
}7

8
protected override void OnExecute() {9
using (OptionForm optionForm = new OptionForm(base.mainForm)) {10
Core.Properties memento = mainForm.CreateMemento();11
byte previewOpacity = clockOpt.PreviewOpacity;12
if (optionForm.ShowDialog() == DialogResult.Cancel) {13
mainForm.SetMemento(memento);14
base.clockOpt.PreviewOpacity = previewOpacity;15
mainForm.RefreshSkin();16
} else { // OK clicked17
clockOpt.PreviewOpacity = clockOpt.Opacity;18
Point location = mainForm.Location;19
mainForm.CheckBounds(ref location);20
mainForm.Location = location;21
mainForm.RefreshWindow();22
}23
}24
}25
} 1
// RemindElement.cs
2
internal class RemindElement : Element {
3
private Timer timerRemind;
4
5
public RemindElement(Mediator mediator, ToolStripMenuItem source)
6
: base(mediator) {
7
base.command = new MenuItemCommand(source);
8
timerRemind = new Timer();
9
this.timerRemind.Interval = 60000;
10
this.timerRemind.Tick += TimerRemindOnTick;
11
this.timerRemind.Enabled = clockOpt.HaveRemind;
12
}
13
14
protected override void OnExecute() {
15
using (RemindListForm frmRemind = new RemindListForm(remindOperate)) {
16
Core.Properties savedProperties = remindOperate.CreateMemento();
17
if (frmRemind.ShowDialog() == DialogResult.OK) {
18
Core.Properties properties = remindOperate.CreateMemento();
19
PropertyService.Set("WinClock.RemindOperate", properties);
20
clockOpt.HaveRemind = frmRemind.HaveRemind;
21
} else { // User Canceled
22
remindOperate.SetMemento(savedProperties);
23
}
24
}
25
}
26
27
protected internal override void OnStatusChanged() {
28
if (clockOpt.HaveRemind) {
29
if (!this.timerRemind.Enabled) {
30
this.timerRemind.Start();
31
}
32
} else {
33
if (!this.timerRemind.Enabled) {
34
this.timerRemind.Stop();
35
}
36
}
37
}
38
39
protected override void Dispose(bool disposing) {
40
if (disposing) {
41
try {
42
timerRemind.Tick -= TimerRemindOnTick;
43
timerRemind.Dispose();
44
} finally {
45
timerRemind = null;
46
}
47
}
48
49
base.Dispose(disposing);
50
}
51
52
private void TimerRemindOnTick(object sender, EventArgs e) {
53
remindOperate.CheckRemindList();
54
}
55
}
RemindOperate 类实现的是对定时提醒任务的检测和保存,它利用一个 Timer, 每隔一
// RemindElement.cs2
internal class RemindElement : Element {3
private Timer timerRemind;4

5
public RemindElement(Mediator mediator, ToolStripMenuItem source)6
: base(mediator) {7
base.command = new MenuItemCommand(source);8
timerRemind = new Timer();9
this.timerRemind.Interval = 60000;10
this.timerRemind.Tick += TimerRemindOnTick;11
this.timerRemind.Enabled = clockOpt.HaveRemind;12
}13

14
protected override void OnExecute() {15
using (RemindListForm frmRemind = new RemindListForm(remindOperate)) {16
Core.Properties savedProperties = remindOperate.CreateMemento();17
if (frmRemind.ShowDialog() == DialogResult.OK) {18
Core.Properties properties = remindOperate.CreateMemento();19
PropertyService.Set("WinClock.RemindOperate", properties);20
clockOpt.HaveRemind = frmRemind.HaveRemind;21
} else { // User Canceled22
remindOperate.SetMemento(savedProperties);23
}24
}25
}26

27
protected internal override void OnStatusChanged() {28
if (clockOpt.HaveRemind) {29
if (!this.timerRemind.Enabled) {30
this.timerRemind.Start();31
}32
} else {33
if (!this.timerRemind.Enabled) {34
this.timerRemind.Stop();35
}36
}37
}38

39
protected override void Dispose(bool disposing) {40
if (disposing) {41
try {42
timerRemind.Tick -= TimerRemindOnTick;43
timerRemind.Dispose();44
} finally {45
timerRemind = null;46
}47
}48

49
base.Dispose(disposing);50
}51

52
private void TimerRemindOnTick(object sender, EventArgs e) {53
remindOperate.CheckRemindList();54
}55
}分钟检测一次是否有提醒任务。
1
// RemindOperate.cs
2
[Serializable()]
3
public struct RemindData : IMementoCapable {
4
public string remindTitle;
5
public string remindMode;
6
public string remindTime;
7
public string remindText;
8
public string programPath;
9
public string musicPath;
10
public bool showMessage;
11
public bool executeprogram;
12
public bool playSound;
13
public bool closeComputer;
14
15
IMementoCapable Members
47
}
48
49
[Serializable()]
50
public class RemindOperate : IMementoCapable, ILacalizable {
51
private List<RemindData> remindDataList;
52
public static readonly string YearMonthDayTimeFmt = "yyyy-MM-dd HH:mm";
53
public static readonly string YearMonthDayFmt = "yyyy-MM-dd";
54
public static readonly string MonthDayFmt = "MM-dd";
55
public static readonly string DayFmt = "dd";
56
public static readonly string TimeFmt = "HH:mm";
57
58
public static readonly Dictionary<DayOfWeek, string> Days;
59
static RemindOperate() {
60
Days = new Dictionary<DayOfWeek, string>();
61
Initialize();
62
}
63
64
public RemindOperate() {
65
remindDataList = new List<RemindData>();
66
ResourceService.LocalizeList.Add(this);
67
}
68
69
public List<RemindData> RemindDataList {
70
get {
71
return this.remindDataList;
72
}
73
}
74
75
public void CheckRemindList() {
76
string currentTime = DateTime.Now.ToString(YearMonthDayTimeFmt);
77
currentTime += " " + Days[DateTime.Now.DayOfWeek];
78
for (int index = 0; index < remindDataList.Count; ++index) {
79
RemindData rmdData = remindDataList[index];
80
string remindTime = rmdData.remindTime;
81
82
if (currentTime.IndexOf(remindTime) != -1) {
83
ShowRemindInfo(index);
84
}
85
}
86
}
87
88
public void ShowRemindInfo(int index) {
89
RemindData rmdData = remindDataList[index];
90
if (rmdData.closeComputer) {
91
MessageService.ShowShutdownMessage();
92
} else {
93
if (rmdData.playSound) {
94
SoundPlayer soundPlayer = new SoundPlayer(rmdData.musicPath);
95
soundPlayer.Play();
96
}
97
98
if (rmdData.showMessage) {
99
MessageService.ShowRemindMessage(rmdData);
100
}
101
102
if (rmdData.executeprogram) {
103
Process proc = new Process();
104
proc.StartInfo.FileName = rmdData.programPath;
105
proc.StartInfo.Arguments = string.Empty;
106
proc.Start();
107
}
108
}
109
}
110
111
private static void Initialize() {
112
Days.Clear();
113
Days.Add(DayOfWeek.Sunday, ResourceService.GetString("CSharpCode.Core.RemindOperate.Sunday"));
114
Days.Add(DayOfWeek.Monday, ResourceService.GetString("CSharpCode.Core.RemindOperate.Monday"));
115
Days.Add(DayOfWeek.Tuesday, ResourceService.GetString("CSharpCode.Core.RemindOperate.Tuesday"));
116
Days.Add(DayOfWeek.Wednesday, ResourceService.GetString("CSharpCode.Core.RemindOperate.Wednesday"));
117
Days.Add(DayOfWeek.Thursday, ResourceService.GetString("CSharpCode.Core.RemindOperate.Thursday"));
118
Days.Add(DayOfWeek.Friday, ResourceService.GetString("CSharpCode.Core.RemindOperate.Friday"));
119
Days.Add(DayOfWeek.Saturday, ResourceService.GetString("CSharpCode.Core.RemindOperate.Saturday"));
120
}
121
122
IMementoCapable Members
148
149
ILacalizable Members
156
}
在这个软件中,Properties类是Memento的关键,同时也是以后要讲的持久化存储的关键。这里只要看他是如何设置和取得一个
// RemindOperate.cs2
[Serializable()]3
public struct RemindData : IMementoCapable {4
public string remindTitle;5
public string remindMode;6
public string remindTime;7
public string remindText;8
public string programPath;9
public string musicPath;10
public bool showMessage;11
public bool executeprogram;12
public bool playSound;13
public bool closeComputer;14

15
IMementoCapable Members47
}48

49
[Serializable()]50
public class RemindOperate : IMementoCapable, ILacalizable {51
private List<RemindData> remindDataList;52
public static readonly string YearMonthDayTimeFmt = "yyyy-MM-dd HH:mm";53
public static readonly string YearMonthDayFmt = "yyyy-MM-dd";54
public static readonly string MonthDayFmt = "MM-dd";55
public static readonly string DayFmt = "dd";56
public static readonly string TimeFmt = "HH:mm";57

58
public static readonly Dictionary<DayOfWeek, string> Days;59
static RemindOperate() {60
Days = new Dictionary<DayOfWeek, string>();61
Initialize();62
}63

64
public RemindOperate() {65
remindDataList = new List<RemindData>();66
ResourceService.LocalizeList.Add(this);67
}68

69
public List<RemindData> RemindDataList {70
get {71
return this.remindDataList;72
}73
}74

75
public void CheckRemindList() {76
string currentTime = DateTime.Now.ToString(YearMonthDayTimeFmt);77
currentTime += " " + Days[DateTime.Now.DayOfWeek];78
for (int index = 0; index < remindDataList.Count; ++index) {79
RemindData rmdData = remindDataList[index];80
string remindTime = rmdData.remindTime;81

82
if (currentTime.IndexOf(remindTime) != -1) {83
ShowRemindInfo(index);84
}85
}86
}87

88
public void ShowRemindInfo(int index) {89
RemindData rmdData = remindDataList[index];90
if (rmdData.closeComputer) {91
MessageService.ShowShutdownMessage();92
} else {93
if (rmdData.playSound) {94
SoundPlayer soundPlayer = new SoundPlayer(rmdData.musicPath);95
soundPlayer.Play();96
}97

98
if (rmdData.showMessage) {99
MessageService.ShowRemindMessage(rmdData);100
}101

102
if (rmdData.executeprogram) {103
Process proc = new Process();104
proc.StartInfo.FileName = rmdData.programPath;105
proc.StartInfo.Arguments = string.Empty;106
proc.Start();107
}108
}109
}110

111
private static void Initialize() {112
Days.Clear();113
Days.Add(DayOfWeek.Sunday, ResourceService.GetString("CSharpCode.Core.RemindOperate.Sunday"));114
Days.Add(DayOfWeek.Monday, ResourceService.GetString("CSharpCode.Core.RemindOperate.Monday"));115
Days.Add(DayOfWeek.Tuesday, ResourceService.GetString("CSharpCode.Core.RemindOperate.Tuesday"));116
Days.Add(DayOfWeek.Wednesday, ResourceService.GetString("CSharpCode.Core.RemindOperate.Wednesday"));117
Days.Add(DayOfWeek.Thursday, ResourceService.GetString("CSharpCode.Core.RemindOperate.Thursday"));118
Days.Add(DayOfWeek.Friday, ResourceService.GetString("CSharpCode.Core.RemindOperate.Friday"));119
Days.Add(DayOfWeek.Saturday, ResourceService.GetString("CSharpCode.Core.RemindOperate.Saturday"));120
}121

122
IMementoCapable Members148

149
ILacalizable Members156
}属性的。主要是通过Get<T>,Set<T>两个泛型方法:
1
// Part of Properties.cs
2
public class Properties {
3
Dictionary<string, object> properties = new Dictionary<string, object>();
4
protected EventHandlerList Events = new EventHandlerList();
5
private static readonly object EventPropertiesChanged = new object();
6
7
public string this[string property] {
8
get {
9
return Convert.ToString(Get(property));
10
}
11
set {
12
Set(property, value);
13
}
14
}
15
16
public event EventHandler<PropertyChangedEventArgs> PropertyChanged {
17
add {
18
this.Events.AddHandler(EventPropertiesChanged, value);
19
}
20
remove {
21
this.Events.RemoveHandler(EventPropertiesChanged, value);
22
}
23
}
24
25
public string[] Elements {
26
get {
27
lock (properties) {
28
List<string> ret = new List<string>();
29
foreach (KeyValuePair<string, object> property in properties) {
30
ret.Add(property.Key);
31
}
32
return ret.ToArray();
33
}
34
}
35
}
36
37
public object Get(string property) {
38
lock (properties) {
39
object val;
40
properties.TryGetValue(property, out val);
41
return val;
42
}
43
}
44
45
public void Set<T>(string property, T value) {
46
T oldValue = default(T);
47
lock (properties) {
48
if (!properties.ContainsKey(property)) {
49
properties.Add(property, value);
50
} else {
51
oldValue = Get<T>(property, value);
52
properties[property] = value;
53
}
54
}
55
OnPropertyChanged(new PropertyChangedEventArgs(this, property, oldValue, value));
56
}
57
58
public bool Contains(string property) {
59
lock (properties) {
60
return properties.ContainsKey(property);
61
}
62
}
63
64
public int Count {
65
get {
66
lock (properties) {
67
return properties.Count;
68
}
69
}
70
}
71
72
public bool Remove(string property) {
73
lock (properties) {
74
return properties.Remove(property);
75
}
76
}
77
78
public override string ToString() {
79
lock (properties) {
80
StringBuilder sb = new StringBuilder();
81
sb.Append("[Properties:{");
82
foreach (KeyValuePair<string, object> entry in properties) {
83
sb.Append(entry.Key);
84
sb.Append("=");
85
sb.Append(entry.Value);
86
sb.Append(",");
87
}
88
sb.Append("}]");
89
return sb.ToString();
90
}
91
}
92
93
public T Get<T>(string property, T defaultValue) {
94
lock (properties) {
95
object o;
96
if (!properties.TryGetValue(property, out o)) {
97
properties.Add(property, defaultValue);
98
return defaultValue;
99
}
100
101
if (o is string && typeof(T) != typeof(string)) {
102
TypeConverter c = TypeDescriptor.GetConverter(typeof(T));
103
try {
104
o = c.ConvertFromInvariantString(o.ToString());
105
} catch (Exception ex) {
106
MessageBox.Show("Error loading property '" + property + "': " + ex.Message);
107
o = defaultValue;
108
}
109
properties[property] = o; // store for future look up
110
} else if (o is ArrayList && typeof(T).IsArray) {
111
ArrayList list = (ArrayList)o;
112
Type elementType = typeof(T).GetElementType();
113
Array arr = System.Array.CreateInstance(elementType, list.Count);
114
TypeConverter c = TypeDescriptor.GetConverter(elementType);
115
try {
116
for (int i = 0; i < arr.Length; ++i) {
117
if (list[i] != null) {
118
arr.SetValue(c.ConvertFromInvariantString(list[i].ToString()), i);
119
}
120
}
121
o = arr;
122
} catch (Exception ex) {
123
MessageBox.Show("Error loading property '" + property + "': " + ex.Message);
124
o = defaultValue;
125
}
126
properties[property] = o; // store for future look up
127
} else if (!(o is string) && typeof(T) == typeof(string)) {
128
TypeConverter c = TypeDescriptor.GetConverter(typeof(T));
129
if (c.CanConvertTo(typeof(string))) {
130
o = c.ConvertToInvariantString(o);
131
} else {
132
o = o.ToString();
133
}
134
}
135
try {
136
return (T)o;
137
} catch (NullReferenceException) {
138
// can happen when configuration is invalid -> o is null and a value type is expected
139
return defaultValue;
140
}
141
}
142
}
143
144
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) {
145
EventHandler<PropertyChangedEventArgs> handler = this.Events[EventPropertiesChanged] as EventHandler<PropertyChangedEventArgs>;
146
if (handler != null) {
147
handler(this, e);
148
}
149
}
150
151
// Other methods
152
}
参考资料:
// Part of Properties.cs 2
public class Properties {3
Dictionary<string, object> properties = new Dictionary<string, object>();4
protected EventHandlerList Events = new EventHandlerList();5
private static readonly object EventPropertiesChanged = new object();6

7
public string this[string property] {8
get {9
return Convert.ToString(Get(property));10
}11
set {12
Set(property, value);13
}14
}15

16
public event EventHandler<PropertyChangedEventArgs> PropertyChanged {17
add {18
this.Events.AddHandler(EventPropertiesChanged, value);19
}20
remove {21
this.Events.RemoveHandler(EventPropertiesChanged, value);22
}23
}24

25
public string[] Elements {26
get {27
lock (properties) {28
List<string> ret = new List<string>();29
foreach (KeyValuePair<string, object> property in properties) {30
ret.Add(property.Key);31
}32
return ret.ToArray();33
}34
}35
}36

37
public object Get(string property) {38
lock (properties) {39
object val;40
properties.TryGetValue(property, out val);41
return val;42
}43
}44

45
public void Set<T>(string property, T value) {46
T oldValue = default(T);47
lock (properties) {48
if (!properties.ContainsKey(property)) {49
properties.Add(property, value);50
} else {51
oldValue = Get<T>(property, value);52
properties[property] = value;53
}54
}55
OnPropertyChanged(new PropertyChangedEventArgs(this, property, oldValue, value));56
}57

58
public bool Contains(string property) {59
lock (properties) {60
return properties.ContainsKey(property);61
}62
}63

64
public int Count {65
get {66
lock (properties) {67
return properties.Count;68
}69
}70
}71

72
public bool Remove(string property) {73
lock (properties) {74
return properties.Remove(property);75
}76
}77

78
public override string ToString() {79
lock (properties) {80
StringBuilder sb = new StringBuilder();81
sb.Append("[Properties:{");82
foreach (KeyValuePair<string, object> entry in properties) {83
sb.Append(entry.Key);84
sb.Append("=");85
sb.Append(entry.Value);86
sb.Append(",");87
}88
sb.Append("}]");89
return sb.ToString();90
}91
}92

93
public T Get<T>(string property, T defaultValue) {94
lock (properties) {95
object o;96
if (!properties.TryGetValue(property, out o)) {97
properties.Add(property, defaultValue);98
return defaultValue;99
}100

101
if (o is string && typeof(T) != typeof(string)) {102
TypeConverter c = TypeDescriptor.GetConverter(typeof(T));103
try {104
o = c.ConvertFromInvariantString(o.ToString());105
} catch (Exception ex) {106
MessageBox.Show("Error loading property '" + property + "': " + ex.Message);107
o = defaultValue;108
}109
properties[property] = o; // store for future look up110
} else if (o is ArrayList && typeof(T).IsArray) {111
ArrayList list = (ArrayList)o;112
Type elementType = typeof(T).GetElementType();113
Array arr = System.Array.CreateInstance(elementType, list.Count);114
TypeConverter c = TypeDescriptor.GetConverter(elementType);115
try {116
for (int i = 0; i < arr.Length; ++i) {117
if (list[i] != null) {118
arr.SetValue(c.ConvertFromInvariantString(list[i].ToString()), i);119
}120
}121
o = arr;122
} catch (Exception ex) {123
MessageBox.Show("Error loading property '" + property + "': " + ex.Message);124
o = defaultValue;125
}126
properties[property] = o; // store for future look up127
} else if (!(o is string) && typeof(T) == typeof(string)) {128
TypeConverter c = TypeDescriptor.GetConverter(typeof(T));129
if (c.CanConvertTo(typeof(string))) {130
o = c.ConvertToInvariantString(o);131
} else {132
o = o.ToString();133
}134
}135
try {136
return (T)o;137
} catch (NullReferenceException) {138
// can happen when configuration is invalid -> o is null and a value type is expected139
return defaultValue;140
}141
}142
}143

144
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) {145
EventHandler<PropertyChangedEventArgs> handler = this.Events[EventPropertiesChanged] as EventHandler<PropertyChangedEventArgs>;146
if (handler != null) {147
handler(this, e);148
}149
}150
151
// Other methods 152
}李建忠 C#面向对象设计模式纵横谈(21):(行为型模式) Memento 备忘录模式
SharpDevelop 3.0 源代码
这是微软技术的一贯特点,使用简单。但是如果要深入的话,还是要投入不少精力的


浙公网安备 33010602011771号