我的 WinClock 项目系列之四 (Memento 模式的应用)

动机 (Motivation)
    在软件的构建过程中,某些对象的状态在转换过程中,可能由于某种需要,
    要求程序能够回溯到对象之前某个点时的状态,如果使用一些公有接口来让
    其他对象得到对象的状态,便会暴露对象的细节实现。
   
    如何实现对象状态的良好保存与恢复?但同时又不会因此而破坏对象本身的
    封装性。
   
意图 (Intent)
    在不破坏封装性的前提下,捕获一个对象的内部状态,并在这个对象之外保存
    这个状态。这样就可以将对象恢复到原先保存的状态。
                                                《设计模式》———— GOF
                                               
从类图可以看出, ClockOption, MainForm, RemindData, RemindOparate 都实现了
一个 IMementoCapable 接口,这个接口定义如下:
1// IMementoCapable.cs
2public interface IMementoCapable {
3    Properties CreateMemento();
4    void SetMemento(Properties properties);
5}

很简单,只是能够把一个对象的状态保存为一个Properties对象,或者利用一个Properties
对象把这个对象的状态恢复到之前的状态。
这样做是很有必要的,比如对于 OptionForm,是需要可以预览的,这样用户在更改了一个
设置后马上就可以看到效果。比如拖动 Size 滑块,马上可以看到变大或者变小的效果。这样
的功能用到的还不少,使用 Memento 在窗体加载前先保存好原有的状态,在取消后就可以很
方便到恢复到之前的状态。下面是 Option 菜单执行的操作:
 1// OptionElement.cs
 2internal 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 菜单要执行的操作:
 1// RemindElement.cs
 2internal 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, 每隔一
分钟检测一次是否有提醒任务。

  1// RemindOperate.cs
  2[Serializable()]
  3public 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()]
 50public 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的关键,同时也是以后要讲的持久化存储的关键。这里只要看他是如何设置和取得一个
属性的。主要是通过Get<T>,Set<T>两个泛型方法:


  1// Part of Properties.cs 
  2public class Properties {
  3    Dictionary<stringobject> properties = new Dictionary<stringobject>();
  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<stringobject> 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<stringobject> 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}
参考资料:
  李建忠 C#面向对象设计模式纵横谈(21):(行为型模式) Memento 备忘录模式
  SharpDevelop 3.0 源代码



posted on 2008-06-20 11:40  优哉@游哉  阅读(2422)  评论(2编辑  收藏  举报

导航