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对象,从而实现一些非常巧妙的组合应用。

猜你喜欢

转载自blog.csdn.net/bwangel/article/details/8083368