程序设计实例(3)
最近在指导一个新同事完成一个C#的程序,本文是我对他程序在设计上的一些意见。
1.要求:
做一个等待后台长时间操作的控件,在等待过程中该控件要显示一个等待画面,后台处理完成后自动关闭。
2.过程:
该新同事很快完成了任务,原因是他从网上下载了一个现成的Demo。我检查后确认:该Demo满足我的要求,并且该同事也理解了其源代码,因此我承认他已经完成了任务。
3.分析:
由于该源代码来自网上,代表一般的软件代码,提一些我的意见。
首先,来看一下源代码(下载):源代码主要有3个类:
MainWindow:主画面
WaitingDlg:显示等待画面;
ILongTimeTask:后台长时间操作的任务接口,调用者需要实现该接口。
调用方式:
WaitingDlg dlg =new WaitingDlg(new LongTimeTaskAbc()); dlg.Owner = this; dlg.ShowInTaskbar = false; dlg.ShowDialog();
其中的LongTimeTaskAbc是继承ILongTimeTask,由用户实现后台长时间操作任务。从结构上来看是相当简单,合理的。
WaitingDlg:
public delegate void TaskEndNotify(Object result); public partial class WaitingDlg { public Object TaskResult{get { return m_taskResult; }} private Object m_taskResult; private bool m_bCloseByMe; private readonly ILongTimeTask m_task; private readonly string m_strThePromptText; public WaitingDlg(ILongTimeTask task, string strThePromptText=null) { m_task = task; m_strThePromptText = strThePromptText; InitializeComponent(); } private delegate void CloseMethod(); public void TaskEnd(Object result) { m_taskResult = result; m_bCloseByMe = true; Dispatcher.BeginInvoke(new CloseMethod(Close)); } private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { if (!m_bCloseByMe) { e.Cancel = true; } } private void Window_Loaded(object sender, RoutedEventArgs e) { if (!string.IsNullOrEmpty(m_strThePromptText)) { tbPrompt.Text = m_strThePromptText; } m_task.Start(this); }
LongTimeTaskAbc(调用者实现)
class LongTimeTaskAbc : ILongTimeTask { private Thread m_threadWorking; private WaitingDlg m_dlgWaiting; public void Start(WaitingDlg dlg) { m_dlgWaiting = dlg; m_threadWorking = new Thread(Working); m_threadWorking.Start(); } private void Working() { Thread.Sleep(2000); m_dlgWaiting.TaskEnd(null); } }
从以上源代码看出了:WaitingDlg包含了一个ILongTimeTask对象,调用了ILongTimeTask.Start(),LongTimeTaskAbc也包含了一个WaitingDlg对象,调用了WaitingDlg.TaskEnd().这种相互引用和调用我是坚决反对的。作者的目的无非是在WaitingDlg中启动ILongTimeTask.Start(),在LongTimeTaskAbc中,处理结束时调用WaitingDlg.TaskEnd()来关闭等待画面。完全可以使用回调机制或事件通知来处理这种问题,降低模块间的耦合度。别忘了LongTimeTaskAbc还是一个用户模块,和你的控件模块高度耦合是不对的。
另外一个问题就是:LongTimeTaskAbc中的Start()函数,该函数就是启动一个线程来完成后台长时间操作。这个控件的核心就是启动一个线程来完成后台长时间操作,而LongTimeTaskAbc是用户模块,也就是说你写个控件,核心还得用户来帮你完成,搞毛啊!并且使用上也可以看出问题:用户每次创建一个新的成后台操作任务,都要重复实现ILongTimeTask.Start()的相同代码。一种解决的方法是把ILongTimeTask变成基类,实现Start()的功能,但更好的方法是把ILongTimeTask.Start()放在WaitingDlg中去实现,这才更符合程序设计原则。