DataTracker -- 窗體之間的聯動, 觀察者模式的另類實現
主要用於解決:在一個窗體中修改了資料,而另一個使用此資料的窗體實現自動的資料更新。
它有以下特點:
1) 物件之間的鬆藕合性。兩個窗體或多個窗體之間,相互之間不需要知道對方的存在。
2) 可以實現同一應用程式中,多個子窗體之間的聯動;
3) 也可以實現分散式的聯動。即A計算機修改了資料,依賴此資料的B計算機中的窗體進行自動重新整理。
假如此DataTracker進行了封裝可以直接呼叫,首先看用法:
介面定義:
/// <summary> /// 定義可以重新整理的窗體或控制元件的介面 /// </summary> public interface ICanReLoad { /// <summary> /// 重新整理的窗體或控制元件 /// </summary> void ReLoad(); } /// <summary> /// 可以跟蹤別的物件的修改並觸發預設行為 /// </summary> public interface ITracker : ICanReLoad { /// <summary> /// 被跟蹤物件的字串名稱表,以逗號分隔 /// </summary> string TrackTypes { get; } /// <summary> /// 指示是否是在被跟蹤物件改變時是否立即重新獲取資料 /// 如果為否則等控制元件在被啟用時才重新整理。 /// </summary> bool FastReload { get; set; } } /// <summary> /// 可以跟蹤別的物件的修改並觸發指定名稱的行為 /// </summary> public interface INamedTracker : ITracker { /// <summary> /// 重新整理窗體或控制元件 /// </summary> /// <param name="typeName">指定的名稱</param> void ReLoad(string typeName); }
也就是說,要實現窗體之間的聯動,各聯動窗體要實現以上介面。如果是整個窗體的重新整理,實現ITracker介面,實現區域性重新整理,實現INamedTracker介面。
實現以上介面以上,整個應用程式的聯動管理類是DataTracker工具類。它實現總控。它的方法有:
/// <summary> /// 源物件在修改時通知對應的跟蹤它的物件 /// </summary> /// <param name="sourceTypes">用逗號隔開的資料型別名稱列表</param> public static void Change(params string[] sourceTypes) /// <summary> /// 通知本地和遠端同時更新介面資料 /// </summary> /// <param name="sourceTypes">要更新的資料名稱列表</param> public static void ChangeWithRemote(params string[] sourceTypes) /// <summary> /// 判斷並重新整理指定的跟蹤物件 /// </summary> /// <param name="tracker"></param> public static void ReLoad(ITracker tracker) /// <summary> /// 判斷並重新整理指定名稱的跟蹤物件 /// </summary> /// <param name="tracker"></param> /// <param name="typeName"></param> public static void ReLoad(INamedTracker tracker, string typeName) /// <summary> /// 註冊跟蹤物件 /// </summary> /// <param name="tracker"></param> public static void Register(ITracker tracker) /// <summary> /// 反註冊跟蹤物件 /// </summary> /// <param name="tracker"></param> public static void UnRegister(ITracker tracker)
以上限於篇幅,只列出了DataTracker中的方法簽名並沒有列出實現。它的實現也很簡單,大體是維護一個跟蹤列表,當收到用Change()方法接收的聯動訊號以後,
遍歷跟蹤表,依次呼叫窗體的ReLoad()方法實現聯動。
總之,使用它的方法是:
1) 窗體實現ITracker介面或INamedTracker介面, 其中,TrackTypes屬性中列出本窗體有關的資料項名稱。這個資料項名稱可以任意,但是必須和傳送方的名稱相同。
2) 用DataTracker.Register()方法註冊本窗體到跟蹤列表中。
3 ) 什麼都不管了,坐等其他窗體發信息。
示例:
//窗體A, 展示資料的窗體
public class FormA:Form,ITracker
{
public string TrackTypes { get {return "資料A";} }
public FormA()
{
InitializeComponent();
DataTracker.Register(this);
}
public void ReLoad()
{
//重新整理資料A
}
protected void FormA_Closed(object sender, EventArgs e)
{//對於窗體或控制元件而言,如果不在關閉時登出跟蹤項,後果會很嚴重
DataTracker.Unregister(this);
}
}
//窗體B, 修改了資料的窗體
public class FormB:Form
{
public void SaveDataA()
{
//修改資料A
// ...
//發出訊號
DataTracker.Change("資料A");
}
}
以上是實現ITracker介面的窗體聯動的例子,最為簡單。
如果實現INamedTracker介面,則稍稍複雜一點,可能要用一系列case語句來路由判斷要重新整理哪一部分資料。這裡就先不贅述了。
以上FormA中的Register和UnRegister等方法,可以在基類窗體中作進一步封裝,以進一步簡化業務窗體的使用。
畢竟,要實現一個複雜的應用程式,很少有窗體只用到Form這個基類的。
下面還有最激動人心的,分散式的聯動,我可以把這部分程式碼整個晒出來。因為很簡單:
/// <summary>
/// 遠端跟蹤類:用於遠端通知並接收資料的更新訊號
/// </summary>
public class RemoteTracker
{
/// <summary>
/// 最後一次更新的時間
/// </summary>
DateTime lastUpdateTime = ServerHelper.ServerTime;
/// <summary>
/// 從服務端獲取資料的更新訊號
/// </summary>
/// <returns></returns>
string GetRemoteChangedTypes()
{
string r = WebHelper.GetWebResponseText(
String.Format("{0}/DataTrackerService.ashx?dt={1}&{2}",
XpoFactory.Instance.ServiceUrl,
HttpUtility.UrlEncode(lastUpdateTime.ToString("yyyy-MM-dd HH:mm:ss")),
LoginMgr.Instance.VerifyQuery));
return r;
}
/// <summary>
/// 將本地資料的更新訊號發到服務端
/// 再由服務端通知其他客戶端更新
/// </summary>
/// <param name="trackTypes">發生變更的資料名稱列表 </param>
public string TellRemoteChanged(params string[] trackTypes)
{
string r = WebHelper.GetWebResponseText(
String.Format("{0}/DataTrackerService.ashx?tt={1}&dt={2}&{3}",
XpoFactory.Instance.ServiceUrl,
HttpUtility.UrlEncode(String.Join(",", trackTypes)),
HttpUtility.UrlEncode(ServerHelper.ServerTime.ToString("yyyy-MM-dd HH:mm:ss")),
LoginMgr.Instance.VerifyQuery));
return r;
}
System.Timers.Timer timer = new System.Timers.Timer(10000); //預設10秒一次
/// <summary>
/// 遠端跟蹤物件單例
/// 用於遠端通知並接收資料的更新訊號
/// </summary>
public static RemoteTracker Instance = new RemoteTracker();
private RemoteTracker()
{
}
/// <summary>
/// 開始執行跟蹤
/// </summary>
public void Start()
{
timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
timer.Start();
}
void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
string types = GetRemoteChangedTypes();
if (!string.IsNullOrEmpty(types))
{
DataTracker.Change(types.Split(','));
}
lastUpdateTime = ServerHelper.ServerTime;
}
}
事實上,分散式聯動其實就是一個輪詢機制,每隔一段時間請求一次伺服器(這裡用的asp.net的ashx服務),獲取最近修改過的資料列表。
在聯動的發起方,要做的事情是在修改資料後,呼叫TellRemoteChanged()方法來通知伺服器。
另外,在DataTracker.ChangeWithRemote()方法中,已經綜合呼叫了本地通知和遠端通知,因此,通過這個方法可以同時發起本地的聯動和遠端其他客戶機的聯動。
雖然此工具類處理窗體是最典型的應用,它也可以處理其他任何object物件,從而實現一些非常巧妙的組合應用。