初学设计模式之策略模式

来源:互联网 发布:安畅网络认证 编辑:程序博客网 时间:2024/06/02 00:52

《大话》中关于策略模式是通过日常生活中常见的事物:商场打折促销引出的。每逢佳节,各大超市,购物中心就会涌现出五花八门的打折促销活动吸引顾客消费。自然,超市的购物结算系统就有了需求:根据不同的打折促销策略计费收银。这个代码怎么写呢?

通常情况下,初学者的思路很直接,他们认为这不是什么问题:直接就在确定按钮的click事件处理程序里,各种if-else ,switch统统搞起,就像下面这样:

///初学者代码///double total=0;//这里只写了click事件的处理程序大概,load事件省略private void btnOK_Click(object sender , EventArgs e){double totalPrice=0d;//根据下拉列表选择相应的打折方式switch(cbxType.selectedIndex){case 0:totalPrice=Convert.todouble(txtPricr.text)*Convert.toDouble(txtNum.text);break;//打八折case 1:totalPrice=Convert.todouble(txtPricr.text)*Convert.toDouble(txtNum.text)*0.8;.......//以下根据需求的变化,省略N段代码.....}}


如果商场的促销手段发生了变化,得,麻烦来了,要动大手术修改先前写的代码,于是乎,加班就不可避免的产生了!!!!

怎么办?策略模式可以很好的解决类似的问题(当然,她不是唯一的解决方法)。它的定义如下:

策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。(原文:The Strategy Pattern defines a family of algorithms,encapsulates each one,and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.)

  Context(应用场景):
  1、需要使用ConcreteStrategy提供的算法。
  2、 内部维护一个Strategy的实例。
  3、 负责动态设置运行时Strategy具体的实现算法。
  4、负责跟Strategy之间的交互和数据传递。
  Strategy(抽象策略类):
  1、 定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,Context使用这个接口调用不同的算法,一般使用接口或抽象类实现。
  ConcreteStrategy(具体策略类):
  2、 实现了Strategy定义的接口,提供具体的算法实现。

商场的各种优惠活动,从本质上看,都是算法的不同,即计算过程不同,折扣有折扣的算法,返利有返利的算法,但是它们都有相同点:返回最后的收费金额(acceptCash方法)。根据面向对象的设计思想,抽取共同部分,形成抽象类。如下:

/// <summary>    /// 抽象算法类(现金收费抽象类)    /// </summary>   abstract class CashSuper    {       //定义算法接口       //收费金额方法       public abstract double acceptCash(double money);    }
具体的策略类:(这里为了简洁,只写其中一个)

 /// <summary>    /// 正常收费子类    /// </summary>   class CashNormal:CashSuper    {        public override double acceptCash(double money)        {            return money;        }    }



刚才说了,三种不同的计算类,调用接口都是一样的,返回实收金额,则,这里添加一个CashContext类,用以维护cashsuper类的引用,根据不同的需求,进行不同的收费。

class CashContext    {        private CashSuper cs;        /// <summary>        /// 构造方法        /// </summary>        /// <param name="csuper">初始化通过构造方法,传入具体的收费策略</param>        public CashContext(CashSuper csuper)        {            this.cs = csuper;        }        /// <summary>        /// 获得计算结果        /// 由于CashSuper是私有的,这里再次封装供外界调用        /// </summary>        /// <param name="money"></param>        /// <returns></returns>        public double GetResult(double money)        {            return cs.acceptCash(money);        }    }


客户端的代码如下:

 /// <summary>    /// 客户端的代码    /// </summary>    public partial class Form1 : Form    {        double total = 0;        public Form1()        {            InitializeComponent();        }        private void Form1_Load(object sender, EventArgs e)        {        }                private void btnOK_Click(object sender, EventArgs e)        {            /*CashContext cc = null;            switch(cmbMethod.SelectedItem.ToString())            {                case "正常收费":                    cc = new CashContext(new CashNormal());                    break;                case "满300返100":                    cc = new CashContext(new CashReturn("300","100"));                    break;                                    case "打八折":                    cc = new CashContext(new CashRebate("0.8"));                    break;            }*/            ///改版后的客户端代码            CashContext csuper = new CashContext(cmbMethod.SelectedItem.ToString());            double totalPrice = 0d;            totalPrice = csuper.GetResult(Convert.ToDouble(txtPrice.Text)*Convert.ToDouble(txtCount.Text));            total += totalPrice;            listBox1.Items.Add("单价:"+txtPrice.Text+"数量:"+txtCount.Text+""+cmbMethod.SelectedItem+"合计:"+totalPrice.ToString());            lblAll.Text = total.ToString();        }    }


仔细看看,觉得这个客户端的代码还是不够好,为什么呢?因为实例化具体的策略对象要由客户端来做,比较繁琐,而且可拓展性比较差,如果以后需求发生了改变,增加了新的策略类,那么客户端就要进行更改。(这就是变化点)

面向对象的宗旨就是封装变化点,如何才能封装客户端的变化?回忆起简单工厂模式,只用通过传入字符串就可以实力化出相应的对象,那么这里是否可以使用同样的方法,使得对于客户端而言,接口粒度再粗一点呢?答案是YES。结合简单工厂模式,更改如下:

///与简单工厂模式相结合    ///CashContext改版    class CashContext    {        private CashSuper cc;        /// <summary>        /// 构造方法        /// </summary>        /// <param name="csuper">初始化通过构造方法,传入字符串参数</param>        public CashContext(string type)        {            switch (type)            {                case "正常收费":                    CashNormal cs0 = new CashNormal();                    cc = cs0;                    break;                case "满300返100":                    CashReturn cs1 = new CashReturn("300","100");                    cc = cs1;                    break;                case "打八折":                    CashRebate cs2 = new CashRebate("0.8");                    cs = cs2;                    break;            }        }        /// <summary>        /// 获得计算结果        /// 由于CashSuper是私有的,这里再次封装供外界调用        /// </summary>        /// <param name="money"></param>        /// <returns></returns>        public double GetResult2(double money)        {            return cs.acceptCash(money);        }    }


则,客户端对应更改为:

 ///改版后的客户端代码            CashContext csuper = new CashContext(cmbMethod.SelectedItem.ToString());            double totalPrice = 0d;            totalPrice = csuper.GetResult(Convert.ToDouble(txtPrice.Text)*Convert.ToDouble(txtCount.Text));            total += totalPrice;            listBox1.Items.Add("单价:"+txtPrice.Text+"数量:"+txtCount.Text+""+cmbMethod.SelectedItem+"合计:"+totalPrice.ToString());            lblAll.Text = total.ToString();

改造后的客户端代码简洁多了。

UML:



总结:今天学习的策略模式和上次学习的简单工厂模式看上去很相似,其实质都是封装变化点,策略模式用于封装不同的算法(规则),在分析过程中遇到需要在不同时间应用不同的业务规则时候,就可以使用策略模式来处理。

原创粉丝点击