zoukankan      html  css  js  c++  java
  • Command模式应用实践

    http://www.cnblogs.com/wayfarer/archive/2007/05/30/507353.html

    一、 需求分析

    在我们开发的一个项目中,需要自己开发一个安装系统,便于部署特定环境下的产品系统。要求的安装界面如图19-1所示:
     SetupUI.gif
    图19-1 安装系统界面
    根据安装步骤的不同,所要执行的安装逻辑也相应不同,然而客户要求的是能够提供“上一步”和“下一步”的操作,以便于随时回退或者继续向前安装。这也是一般安装系统所应具备的功能。
    根据对安装业务的分析,整个安装共分为七个步骤:
    (1)检测本地计算机并初始化安装系统;
    (2)选择要安装的压缩包;
    (3)复制安装所需的临时文件;
    (4)选定安装目录;
    (5)准备安装;
    (6)执行安装;
    (7)安装成功,退出。
    整个安装步骤中,每一步的安装界面都不相同,而这些界面都由特定的User Control体现,例如User Control中的提示信息、按钮、进度条等。

    二、 坏的设计

    既然我们为每一个安装步骤定义了不同的UserControl对象,那么在每次执行安装时,仅需要利用Pannel控件的添加子控件方法即可,例如定义如下的方法:
    private void AddBodyUC(UserControl uc)
    {
     panBody.Controls.Clear();
     panBody.Controls.Add(uc); 
     panBody.Refresh();
    }
    然后我们直接在主窗体中定义若干执行安装的方法,且根据安装的方向分类,例如:
    private void PreviousStep1()
    {}
    private void NextStep1()
    {}
    我们接着定义一个计数器,用于记录安装步骤数:
    private int step = 1;
    同时,定义一个枚举类型,以指定安装的方向是“上一步”,还是“下一步”:
    public enum OpDirection {Previous=0,Next}
    为了调用更加简单,在执行安装的方法基础上,重又定义了一系列的安装方法,并接收OpDirection对象,根据其值以判断执行的方法:
    private void Step1(OpDirection direction)
    {
     switch (direction)
     {
      case OpDirection.Previous:
       PreviousStep1();
       break;
      case OpDirection.Next:
       NextStep1();
       break;
     }
    }
    现在,我们就可以直接在Button的Click事件中调用相关的方法了,例如:
    private void btnNext_Click(object sender, System.EventArgs e)
    {
      switch (step)
      {
       case 1: 
        Step1(OpDirection.Next);     
        break;
       case 2:
        Step2(OpDirection.Next);    
        break;
       case 3:
        Step3(OpDirection.Next);     
        break;
       case 4:
        Step4(OpDirection.Next);      
        break;
       case 5:
        Step5(OpDirection.Next);      
        break;
       case 6:
        Step6(OpDirection.Next);
        break;
       case 7:
        Step7(OpDirection.Next);
        break;
      } 
    }
    这样的设计不可谓不简单,然而也不可谓不拙劣了。实际上,这样的设计实乃面向过程设计之遗毒,而完全丢失了面向对象设计的精神实质。粗略列举一下,至少存在以下缺陷:
    (1)层次混乱,职责不明,众多实现代码都集中在一个主窗体类中,导致一个类过于庞大;
    (2)逻辑虽然简单,然而代码却极为繁杂,且不利于代码的复用;
    (3)极度僵化,尤其不利于可能的扩展;
    (4)思路混乱,结构模糊,不利于代码之阅读,从而影响其他人对代码可能会有的修改。
    总之,这样的设计仿若一个拙劣的工匠胡乱敲打出来的一件半成品,非但谈不上设计之美,竟然连最基本的设计要素也不具备。勉强可说,够用而已。
    那么,应该如何设计才能克服以上的缺陷呢?其实,从上述的设计中我们可以找到一些改善设计的端倪。最重要的设计原则,还是对象的职责划分。例如我们思考一下执行安装的方法,它们属于主窗体对象的职责范围吗?答案显然是否定的。既然这些方法不属于主窗体对象的职责,为什么我们还要将其定义在主窗体类中呢?
    那么,这些方法究竟属于哪一个对象?是每一个安装步骤所定义的UserControl对象吗?似乎是,然而又不尽然。一个UserControl对象,它的职责应该是什么?虽然是为各个安装步骤定义的类,然而它的职责主要还在于安装界面的相关逻辑,例如相关信息在界面上的显示,进度条的进度显示,还包括Button响应Click事件所可能执行的方法。而执行安装,严格说来,却与这些对象无关。
    既然找不到安装方法所属的对象,为什么不为其专门定义相关的对象呢?思路到了这里,就有了豁然开朗的喜悦了。

    三、 引入Command模式

    如果说一个典型的对象应该包括属性和行为,那么仅仅包含行为的对象,毋庸置疑最佳的定义类型就是接口了。在上一节的设计方案中,定义了一系列安装方法,虽然步骤不同,执行的逻辑也不相同,然而由于具有相同的方法签名,因此完全可以统一为一个接口,例如ISetupCommand:
    public interface ISetupCommand
    {
         void ExecuteSetup();
    }
    既然是多个安装步骤都具有该Setup()方法,自然就可以定义相关的类,并使其实现该接口:
    public class Step1SetupCommand:ISetupCommand
    {
         public void ExecuteSetup()
         {
         //实现略;
         }
    }
    如此一来,修改后的设计其类图如图19-2所示:
     comm02.gif
    图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所示:
     comm03.gif
    图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);
      }
         }
    }
    通过引入设计模式,运用职责分离的原理,我们将与安装有关的逻辑剥离出主窗体类定义,使得整个结构清晰简要,职责分明,且因为对可能存在的变化进行了封装,同时也具备了可扩展性,形成了结构之间的松散耦合。这样的完善设计的整个过程虽然会耗费我们项目开发的时间,然而付出的努力并没有付诸东流,改善后的结构才是健壮的、逻辑清楚的,同样也是优雅的设计。

  • 相关阅读:
    【转载】淘宝数据魔方技术架构解析
    【转载】IE6 FORM不能提交的解决办法
    【转载】雷人语录
    【转载】如何使员工更敬业
    搜狐开源镜像连接地址
    【转载】IT工作者应具备的素质(精)
    【转载】一些经典的计算机书籍
    【转载】代码审查:大家都应该做的事情
    【转载】编程目标:开发人员如何提高能力
    SQL日志文件长度过大的处理方法
  • 原文地址:https://www.cnblogs.com/mydriverc/p/1077865.html
Copyright © 2011-2022 走看看