三、 引入Command模式
如果说一个典型的对象应该包括属性和行为,那么仅仅包含行为的对象,毋庸置疑最佳的定义类型就是接口了。在上一节的设计方案中,定义了一系列安装方法,虽然步骤不同,执行的逻辑也不相同,然而由于具有相同的方法签名,因此完全可以统一为一个接口,例如ISetupCommand: public interface ISetupCommand { void ExecuteSetup(); } 既然是多个安装步骤都具有该Setup()方法,自然就可以定义相关的类,并使其实现该接口: public class Step1SetupCommand:ISetupCommand { public void ExecuteSetup() { //实现略; } } 如此一来,修改后的设计其类图如图19-2所示:
图19-2 ISetupCommand类型的类图
熟悉设计模式的读者应该可以看出,上图所示就是一个标准的Command模式实现。确实如此,我们将安装方法看作是一个用户的请求,或者说是命令。由于该命令逻辑对于系统而言是变化的,因此抽象该命令逻辑,使其与其他调用者之间的耦合度松散,是解决这类问题的最佳方案。 注意:如果比较类图结构,我们会发现Command模式、Strategy模式和State模式是完全一样的。事实正是如此,由于它们的设计思想都是对易于变化的部分进行抽象,或为接口,或为抽象类。唯一的区别,就是所抽象的行为职责不同而已,这一点从各自的名字就可以看出。本例中,由于安装方法更近似于用户的请求或命令,所以称其为Command模式更加恰当。 引入Command模式确乎使我们的程序结构更加合理了,然而,我们使用设计模式,并不是要生搬硬套,而应该遵循其设计的基本原则。以本例而言,实则我们没有必要定义诸如Step1SetupCommand的类,来实现ISetupCommand接口。我们只需要修改原来为各个安装步骤定义的UserControl类,令其实现ISetupCommand接口即可。 如此说来,安装行为仍然属于UserControl的职责吗?这岂不是与前面的分析自相矛盾?其实不然,两者之间有着迥然的区别。如果只是将安装方法简单地放到UserControl对象中,则该职责是与具体的UserControl类型相绑定的,例如Step1BodyUC类对象,它们的关系是一种强依赖关系。由于UserControl类类型是.Net Framework已经定义好的,虽然各个具体的UserControl对象有一个共同的基类UserControl,然而安装方法却没有被共同抽象出来,也即是说,各个UserControl对象的安装方法是各自为政的,因此如下的代码就是错误的: UserControl uc = new Step1BodyUC(); uc.ExecuteSetup(); 除非将对象uc进行显示转换为Step1BodyUC类型,然而这样的处理就完全悖离了面向对象思想中的多态性。 如果是各个UserControl对象均实现ISetupCommand接口,情况就完全不同了: public class Step1BodyUC:UserControl,ISetupCommand { //实现略; } 以下代码是合理的: ISetupCommand uc = new Step1BodyUC(); uc.ExecuteSetup(); 修改后的设计方案类图应该如图19-3所示:
图19-3 修改后的设计类图
相比最初的设计,我们仅仅新增加了一个ISetupCommand接口,同时将原来在主窗口类中的安装方法,转移到了各个UserControl对象中,作为ISetupCommand接口方法的实现。
四、 进一步完善
虽然程序结构在引入Command模式后有了很大的改观,然而现有的各个ISetupCommand对象并不能很好地被主窗体对象所调用。在为btnNext和btnPrevious按钮的Click事件实现安装行为时,安装步骤必须是顺次执行的,而如今的设计,并不能体现这样一个顺序关系。唯一的办法,是将这些ISetupCommand对象依次放入一个集合对象中,并提供Next()和Previous()方法,返回正确的对象: public class SetupUCChain { private List<ISetupCommand> m_list; private int m_step; private static SetupUCChain m_chain; private SetupUCChain() { m_list = new List<ISetupCommand>(); m_step = 0; Init(); } public static SetupUCChain CreateSetupUCChain() { if (m_chain == null) { return new SetupUCChain(); } else { return m_chain; } } private void Init() { m_list.Add(new Step1BodyUC()); m_list.Add(new Step2BodyUC()); m_list.Add(new Step3BodyUC()); m_list.Add(new Step4BodyUC()); m_list.Add(new Step5BodyUC()); m_list.Add(new Step6BodyUC()); m_list.Add(new Step7BodyUC()); } public ISetupCommand Next() { ++m_step; if (m_step < m_list.Count) { return (ISetupCommand)m_list[m_step]; } else { throw new IndexOutOfRangeException("Setup is completed."); } }
public ISetupCommand Previous() { --m_step; if (m_step >= 0) { return (ISetupCommand)m_list[m_step]; } else { throw new IndexOutOfRangeException("No previous step."); } } } 考虑到SetupUCChain对象最多只能实例化一次,因此我在此引入了Singleton模式。 最后,由于在各自的安装方法中,需要将UserControl本身添加到主窗体Pannel控件的子控件中,我们还需要修改ISetupCommand的接口方法,从参数传入Pannel控件对象: public interface ISetupCommand { void ExecuteSetup(Pannel pannel); } 那么在各个UserControl对象中,需要在原有的安装方法实现中添加如下的代码: panel.Controls.Add(this); 现在,对于btnPrevious和btnNext按钮的Click事件而言,逻辑就非常简单了: public class SetupMainForm:System.Windows.Forms.Form { private System.Windows.Forms.Panel panBody; private SetupUCChain m_chain = SetupUCChain.CreateSetupUCChain(); //中间代码略; private void btnNext_Click(object sender, System.EventArgs e) { try { m_chain.Next().ExecuteSetup(panBody); } catch (IndexOutOfRangeException ex) { MessageBox.Show(ex.Message); } } private void btnPrevious_Click(object sender, System.EventArgs e) { try { m_chain.Previous().ExecuteSetup(panBody); } catch (IndexOutOfRangeException ex) { MessageBox.Show(ex.Message); } } } 通过引入设计模式,运用职责分离的原理,我们将与安装有关的逻辑剥离出主窗体类定义,使得整个结构清晰简要,职责分明,且因为对可能存在的变化进行了封装,同时也具备了可扩展性,形成了结构之间的松散耦合。这样的完善设计的整个过程虽然会耗费我们项目开发的时间,然而付出的努力并没有付诸东流,改善后的结构才是健壮的、逻辑清楚的,同样也是优雅的设计。  
2/2 首页 上一页 1 2 |