1. 程式人生 > >DataTracker -- 窗體之間的聯動, 觀察者模式的另類實現

DataTracker -- 窗體之間的聯動, 觀察者模式的另類實現

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物件,從而實現一些非常巧妙的組合應用。