zoukankan      html  css  js  c++  java
  • 策略模式

    商场促销

    现在小菜被要求做一个商场促销软件,界面如下所示。

    功能描述:

    输入每件商品的单价和数量,点击确定计算该种类商品的总价值。

    把商品单价、数量、总价值显示到一个ListBox中。

    在最下面显示此次购物的总交费额。

    点击重置,清除所有的数据。总计显示为0。

    image

    小菜上来就写,实现的代码如下:

    public partial class Form1 : Form
        {
            double totalCash = 0;
    
            public Form1()
            {
                InitializeComponent();
            }
    
            private void btnOK_Click(object sender, EventArgs e)
            {
                double sumPrice = Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtQuantity.Text);
                //listbox显示每件商品的价钱
                lbxList.Items.Add("单价: " + txtPrice.Text + " 数量: " + txtQuantity.Text + " 合计: " + sumPrice.ToString());
                //lblResult中显示总价钱
                totalCash += sumPrice;
                lblResult.Text = totalCash.ToString();
            }
    
            private void btnReset_Click(object sender, EventArgs e)
            {
                totalCash = 0;
                txtPrice.Text = "";
                txtQuantity.Text = "";
                lblResult.Text = totalCash.ToString();
                //清除ListBox的内容
                lbxList.Items.Clear();
            }
        }

    但是现在面临一个问题,就是商场要经常打折,不能说是没次打折都要修改totalCash,给他乘以一个系数。小菜的解决办法是增加一个combox下拉列表框。

    image

    public partial class Form1 : Form
        {
            double totalCash = 0;
    
            public Form1()
            {
                InitializeComponent();
            }
    
            private void btnOK_Click(object sender, EventArgs e)
            {
                double sumPrice = 0;
                switch(cbxType.SelectedIndex)
                {
                    case 0:
                        sumPrice = Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtQuantity.Text);
                        break;
                    case 1:
                        sumPrice = Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtQuantity.Text) * 0.8;
                        break;
                    case 2:
                        sumPrice = Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtQuantity.Text) * 0.7;
                        break;
                    case 3:
                        sumPrice = Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtQuantity.Text) * 0.5;
                        break;
                    default:
                        break;
                }
                totalCash += sumPrice;
                    
                //listbox显示每件商品的价钱
                lbxList.Items.Add("单价: " + txtPrice.Text + " 数量: " + txtQuantity.Text + " "+ cbxType.SelectedItem + " 合计: " + sumPrice.ToString());
                //lblResult中显示总价钱
                lblResult.Text = totalCash.ToString();
            }
    
            private void btnReset_Click(object sender, EventArgs e)
            {
                totalCash = 0;
                txtPrice.Text = "";
                txtQuantity.Text = "";
                lblResult.Text = totalCash.ToString();
                //清除ListBox的内容
                lbxList.Items.Clear();
    
                cbxType.SelectedIndex = 0;
            }
    
            private void Form1_Load(object sender, EventArgs e)
            {
                cbxType.Items.AddRange(new object[] {"正常收费", "打八折", "打七折", "打五折"});
                cbxType.SelectedIndex = 0;
            }
        }

    这样写确实比原来灵活了好多,但是重复的代码比较多,Convert.ToDouble()就写了好多遍,而且四个分支要执行的语句几乎没什么不同。所以要考虑一下重构。

    不过最主要的东西来了,现在商场活动加大,需要有满300返100的促销算法,菜鸟一般会直接加一个实现其功能的函数。大鸟的做法就是用简单工厂模式了,先写一个父类,再继承它实现多个打折和返利的子类,利用多态,完成这个代码。这里要找出哪里是相同的,哪里是不同的,不能说八折写个子类,七折写个子类…满200返50写个子类…,这样的话就真的成菜鸟了。

    这里的打折都是一样的,只要有个初始化的参数就可以了。满几送几的,需要两个参数才行。

    面向对象的编程,并不是类越多越好,类的划分是为了封装,但分类的基础是抽象,具有相同属性和功能的对象的抽象集合才是类。

    现金收费抽象类:

    abstract class CashSuper
        {
            //接收参数money:当前价格
            //函数返回值:活动时期的当前价格
            public abstract double AcceptCash(double money);
        }

    正常收费子类:

    class CashNormal : CashSuper
        {
            public override double AcceptCash(double money)
            {
                return money;
            }
        }

    打折收费子类:

    class CashRebate : CashSuper
        {
            private double moneyRebate = 1;
    
            //既然这个类是实现打折的算法,那么在类的构造的时候就要知道打几折
            public CashRebate(string moneyRebate)
            {
                this.moneyRebate = double.Parse(moneyRebate);
            }
    
            public override double AcceptCash(double money)
            {
                return money * moneyRebate;
            }
        }

    返利收费子类:

    class CashReturn : CashSuper
        {
            private double moneyConditon = 0;
            private double moneyReturn = 0;
            //既然这个类是实现返现的算法,那么在类的构造的时候就要知道返现的规则
            public CashReturn(string moneyCondition, string moneyReturn)
            {
                this.moneyConditon = double.Parse(moneyCondition);
                this.moneyReturn = double.Parse(moneyReturn);
            }
    
            public override double AcceptCash(double money)
            {
                double countPoint = Math.Floor(money / moneyConditon);
                money -= countPoint * moneyReturn;
    
                return money;
            }
        }

    现金收费工厂类:

    class CashFactory
        {
            public static CashSuper CreateCashAccept(string type)
            {
                CashSuper cs = null;
    
                switch(type)
                {
                    case "正常收费":
                        cs = new CashNormal();
                        break;
                    case "打8折":
                        cs = new CashRebate("0.8");
                        break;
                    case "满300返100":
                        cs = new CashReturn("300", "100");
                        break;
                    default :
                        break;
                }
    
                return cs;
            }
        }

    客户端程序主要部分:

    public partial class Form1 : Form
        {
            double totalCash = 0;
    
            public Form1()
            {
                InitializeComponent();
            }
    
            private void btnOK_Click(object sender, EventArgs e)
            {
                double sumPrice = 0;
                //计算一下该中商品的总价值
                sumPrice = Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtQuantity.Text);
                //利用工厂生产价格计算类,并调用相应的算法
                CashSuper cs = CashFactory.CreateCashAccept(cbxType.SelectedItem.ToString());
                //更新sumPrice的值,打折或者返现时的值
                sumPrice = cs.AcceptCash(sumPrice);
                totalCash += sumPrice;
                    
                //listbox显示每件商品的价钱
                lbxList.Items.Add("单价: " + txtPrice.Text + " 数量: " + txtQuantity.Text + " "+ cbxType.SelectedItem + " 合计: " + sumPrice.ToString());
                //lblResult中显示总价钱
                lblResult.Text = totalCash.ToString();
            }
    
            private void btnReset_Click(object sender, EventArgs e)
            {
                totalCash = 0;
                txtPrice.Text = "";
                txtQuantity.Text = "";
                lblResult.Text = totalCash.ToString();
                //清除ListBox的内容
                lbxList.Items.Clear();
    
                cbxType.SelectedIndex = 0;
            }
    
            private void Form1_Load(object sender, EventArgs e)
            {
                cbxType.Items.AddRange(new object[] {"正常收费", "打8折", "满300返100"});
                cbxType.SelectedIndex = 0;
            }
        }

    可扩展性:如果增加“满500返200”的活动,只需要修改一下界面的combox和收费对象生成工厂中switch分支就可以实现扩展。

    如果商场增加新的促销手段“满100积分10点”,以后积分到一定的程度可以兑换商品,也是很容易扩张的。

    但是问题依然存在,简单工厂模式虽然也能解决这个问题,但这个模式只是解决对象的创建问题,而且由于工厂本身包括了所有的收费方式,商场是可能经常的更改打折额度和返利额度的,每次维护或者扩展都要改动这个工厂,以致代码需要重新便宜部署,这真是个不好的处理方法。

    策略模式

    策略模式是对算法的包装,是把使用算法的责任和算法本身分割开,委派给不同的对象管理。策略模式通常把一个系列的算法包装到一系列的策略类里面,作为一个抽象策略类的子类。用一句话来说,就是:"准备一组算法,并将每一个算法封装起来,使得它们可以互换。"

    下面是一个示意性的策略模式结构图:

    这个模式涉及到三个角色:

    • 环境(Context)角色:持有一个Strategy类的引用。
    • 抽象策略(Strategy)角色:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
    • 具体策略(ConcreteStrategy)角色:包装了相关的算法或行为。

    对比一下简单工厂的模式结构图,Simple Factory模式角色与结构:


    工厂类角色Creator (LightSimpleFactory):工厂类在客户端的直接控制下(Create方法)创建产品对象。
    抽象产品角色Product (Light):定义简单工厂创建的对象的父类或它们共同拥有的接口。可以是一个类、抽象类或接口。
    具体产品角色ConcreteProduct (BulbLight, TubeLight):定义工厂具体加工出的对象。

    在学习策略模式时,学员常问的一个问题是:为什么不能从策略模式中看出哪一个具体策略适用于哪一种情况呢?

    答案非常简单,策略模式并不负责做这个决定。换言之,应当由客户端自己决定在什么情况下使用什么具体策略角色。策略模式仅仅封装算法,提供新算法插入到已有系统中,以及老算法从系统中"退休"的方便,策略模式并不决定在何时使用何种算法。

    策略模式定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。

    结构示意性代码:

    // "Strategy"
    abstract class Strategy
    {
      // Methods
      abstract public void AlgorithmInterface();
    }
    
    // "ConcreteStrategyA"
    class ConcreteStrategyA : Strategy
    {
      // Methods
      override public void AlgorithmInterface()
      {
        Console.WriteLine("Called ConcreteStrategyA.AlgorithmInterface()");
      }
    }
    
    // "ConcreteStrategyB"
    class ConcreteStrategyB : Strategy
    {
      // Methods
      override public void AlgorithmInterface()
      {
        Console.WriteLine("Called ConcreteStrategyB.AlgorithmInterface()");
      }
    }
    
    // "ConcreteStrategyC"
    class ConcreteStrategyC : Strategy
    {
      // Methods
      override public void AlgorithmInterface()
      {
        Console.WriteLine("Called ConcreteStrategyC.AlgorithmInterface()");
      }
    }
    
    // "Context"
    class Context
    {
      // Fields
      Strategy strategy;
    
      // Constructors
      public Context( Strategy strategy )
      {
        this.strategy = strategy;
      }
    
      // Methods
      public void ContextInterface()
      {
        strategy.AlgorithmInterface();
      }
    }
    
    /// <summary>
    /// Client test
    /// </summary>
    public class Client
    {
      public static void Main( string[] args )
      {
        // Three contexts following different strategies
        Context c = new Context( new ConcreteStrategyA() );
        c.ContextInterface();
    
        Context d = new Context( new ConcreteStrategyB() );
        d.ContextInterface();
    
        Context e = new Context( new ConcreteStrategyC() );
        e.ContextInterface();
      }
    }

    策略模式实现商场收银系统:

    CashContext类实现:

    class CashContext
        {
            private CashSuper cs;
            public CashContext(CashSuper cs)
            {
                this.cs = cs;
            }
            public double GetResult(double money)
            {
                return cs.AcceptCash(money);
            }
        }

    但是这样仅用策略模式的话,客户端就得负责根据不同的方案选择算法,又回到了原来的老路子了。所以这时候要把简单工厂模式和策略模式结合起来用,让CashContext这个类负责收费方式算法的生成。

    改造后的CashContext类的实现:

    class CashContext
        {
            private CashSuper cs;
            public CashContext(string type)
            {
                switch(type)
                {
                    case "正常收费":
                        cs = new CashNormal();
                        break;
                    case "满300返100":
                        cs = new CashReturn("300","100");
                        break;
                    case "打8折":
                        cs = new CashRebate("0.8");
                        break;
                    default :
                        break;
                }
            }
            public double GetResult(double money)
            {
                return cs.AcceptCash(money);
            }
        }
    对比一下CashFactory、CashContext、改进后的CashContext发现:

    简单工厂只是负责造一个这样的对象,然后把对象的引用返回出去,具体怎么用,还是要在客户端中。

    策略模式是给我一个对象的引用,我在里面帮你使用,然后给你一个统一的函数接口,你不容管自己调用的是什么类的引用。

    简单工厂和策略模式结合后:负责制造一个对象,但是这时的对象不用传递出去了,直接在里面调用,然后开放一个统一的函数接口,客户端看不到任何与计算算法有关的类。

    策略模式+简单工厂后的客户端代码:

    public partial class Form1 : Form
        {
            double totalCash = 0;
    
            public Form1()
            {
                InitializeComponent();
            }
    
            private void btnOK_Click(object sender, EventArgs e)
            {
                double sumPrice = 0;
                //计算一下该中商品的总价值
                sumPrice = Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtQuantity.Text);
                //利用工厂生产价格计算类,并调用相应的算法
                CashContext cc = new CashContext(cbxType.SelectedItem.ToString());
                //更新sumPrice的值,打折或者返现时的值
                sumPrice = cc.GetResult(sumPrice);
                totalCash += sumPrice;
                    
                //listbox显示每件商品的价钱
                lbxList.Items.Add("单价: " + txtPrice.Text + " 数量: " + txtQuantity.Text + " "+ cbxType.SelectedItem + " 合计: " + sumPrice.ToString());
                //lblResult中显示总价钱
                lblResult.Text = totalCash.ToString();
            }
    
            private void btnReset_Click(object sender, EventArgs e)
            {
                totalCash = 0;
                txtPrice.Text = "";
                txtQuantity.Text = "";
                lblResult.Text = totalCash.ToString();
                //清除ListBox的内容
                lbxList.Items.Clear();
    
                cbxType.SelectedIndex = 0;
            }
    
            private void Form1_Load(object sender, EventArgs e)
            {
                cbxType.Items.AddRange(new object[] {"正常收费", "打8折", "满300返100"});
                cbxType.SelectedIndex = 0;
            }
        }

    这里还是有问题的,因为CashContext中还是用到了switch,也就是说,如果我们增加一种算法,还是要更改CashContext的代码。

    解决这个问题的办法就是利用反射。

  • 相关阅读:
    django如何给上传的图片重命名(给上传文件重命名)
    Nginx 常见问题解决
    nginx 出现413 Request Entity Too Large问题的解决方法
    python判断一个对象是否可迭代
    InnerClass annotations are missing corresponding EnclosingMember annotations. Such InnerClas...
    Android Studio中新建和引用assets文件
    android 登录效果
    Android (争取做到)最全的底部导航栏实现方法
    抽屉效果的导航菜单
    Android开发之自定义局部导航菜单
  • 原文地址:https://www.cnblogs.com/stemon/p/4167616.html
Copyright © 2011-2022 走看看