初学设计模式之策略模式

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

通常情况下,初学者的思路很直接,他们认为这不是什么问题:直接就在确定按钮的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:



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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值