[置顶] [设计模式学习]设计模式学习之策略模式

需要做一个商场收银软件,营业员根据客户所购买商品的单价和数量,向客户收费。
初步想法是这样的:
用两个文本框来输入单价和数量,一个确定按钮来算出每种商品的费用,用个列表框来记录商品的清单,一个标签来记录总计,用一个重置按钮来重新开始。
商场收银系统v1.1关键代码如下:

//声明一个double变量来计算总计
double total = 0.0d;

private void btnOk_Click(object sender, EventArgs e)
{
    double totalPrices = Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtNum.Text);
    total = total + totalPrices;
    lbxList.Items.Add("单价:"+txtPrice.Text+"数量:"+txtNum.Text+"合计:"+totalPrices.ToString());
    lblResult.Text = total.ToString(); 
}

如果现在商场打折呢?改改改。

double total= 0.0d;
private void Form1_Load(object sender, EventArgs e)
{
    cbxType.Items.AddRange(new object[]{"正常收费", "打八折", "打七折", "打五折"});
    cbxType.SelectedIndex = 0;
}

private void btnOk_Click(object sender, EventArgs e)
{
    double totalPrices = 0d;
    switch(cbxType.SelectedIndex)
    {
        case 0:
            totalPrices = Convert.ToDouble(txtPrice.Text)*Convert.ToDouble(txtNum.Text);
            break;
        case 1:
            totalPrices = Convert.ToDouble(txtPrice.Text)*Convert.ToDouble(txtNum.Text) * 0.8;
            break;
        case 2:
            totalPrices = Convert.ToDouble(txtPrice.Text)*Convert.ToDouble(txtNum.Text) * 0.7;
            break;
        case 3:
            totalPrices = Convert.ToDouble(txtPrice.Text)*Convert.ToDouble(txtNum.Text) * 0.5;
            break;
    }
    total = total + totalPrices;
    lbxList.Item.Add("单价:" + txtPrice.Text + "数量:" + txtNum.Text + " " + cbxType.SelectedItem+"合计:" + totalPrices.ToString());
    lblResult.Text = total.toString();
}

重复代码还是很多,比如Convert.ToDouble()这里写了8遍,而且四个分支要执行的语句除了打折多以外几乎没有什么不同,应该考虑重构一下。不过现在需求又变了,商场活动加大,需要有满300返100的促销算法。现在要用到简单工厂模式了。

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

现金收费抽象类

abstract class CashSuper
{
    public abstract double acceptCash(double money);
}

正常收费子类

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

打折收费子类:

class CashRebate : CashSuper
{
    private double moneyRebate = 1d;
    public CashRebate(string moneyRebate)
    {
        this.moneyRebate = moneyRebate;
    }
    public override double acceptCash(double money)
    {
        return money * moneyRebate;
    }
}

返利收费子类:

class CashReturn : CashSuper
{
    private double moneyCondition = 0.0d;
    private double moneyReturn = 0.0d;
    public CashReturn(string moneyCondition, string moneyReturn)
    {
    //返利收费,初始化时必须要输入返利条件和返利值,比如满300返100,则moneyCondition为300,moneyReturn为100
        this.moneyCondition = double.Parse(moneyCondition);
        this.moneyReturn = double.Parse(moneyReturn);
    }
    public override double acceptCash(double money)
    {
        double result = money;
        //若大于返利条件,则需要减去返利值
        if(money >= moneyCondition)
        {
            result = money - Math.Floor(money / moneyCondition) * moneyReturn;
        }
        return result;
    }
}

现金收费工厂类:

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

客户端主要部分:

double total = 0.0d;
private void btnOk_Click(object sender, EventArgs e)
{
    CashSuper csuper = CashFactory.createCashAccept(cbx.SelectedItem.ToString());
    double totalPrices = 0d;
    totalPrices = csuper.acceptCash(Convert.ToDouble(txtPrice.Text) * Convert.ToDouble(txtNum.Text));
    total = total + totalPrices;
    lbxList.Items.Add("单价:" + txtPrices.Text +"数量:" + txtNum.Text + "合计:" + totalPrices.ToString());
    lblResult.Text = total.ToString();
}

如果商场现在需要增加一种促销手段,满100几分10点,以后积分到一定程度可以领取奖品,怎么做?
简单工厂虽然也能解决这个问题,但这个模式只是解决对象的创建问题,而且由于工厂本身包含了所有的收费方式,商场是可能经常更改打折额度和返利额度,每次维护或扩展收费方式都要改动这个工厂,以致代码需要重新编译部署,这不是最好的处理方法。

策略模式(Strategy):
它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。
Strategy类,支持所有支持的算法的公共接口
//抽象算法类

abstract class Strategy
{
    //算法方法
    public abstract void AlgorithmInterface();
}

ConcreateStrategy,封装了具体的算法或行为,继承于Strategy

//具体算法A
class ConcreteStrategyA : Strategy
{
    //算法A实现方法
    public override void AlgorithmInterface()
    {
        Console.WriteLine("算法A实现");
    }
}
//具体算法B
class ConcreteStrategyB : Strategy
{
    //算法B实现方法
    public override void AlgorithmInterface()
    {
        Console.WriteLine("算法B实现");
    }
}
//具体算法C
class ConcreteStrategyC : Strategy
{
    //算法C实现方法
    public override void AlgorithmInterface()
    {
        Console.WriteLine("算法C实现");
    }
}

Context,用一个ConcreteStrategy来配置,维护对Strategy对象的引用。

//上下文
class Context
{
    Strategy strategy;
    public Context(Strategy strategy)
    {
        this.strategy = strategy;
    }
    //上下文接口
    public void ContextInterface()
    {
        strategy.AlgorithmInterface();
    }
}

客户端代码:

static void Main()
{   
    Context context;
    context = new Context(new ConcreteStrategyA());
    context.ContextInterface();
    context = new Context(new ConcreteStrategyB());
    context.ContextInterface();
    context = new Context(new ConcreteStrategyB());
    context.ContextInterface();
    Console.Read();
}

现在我们利用策略模式来改一下我们之前的代码:
CashContext类:

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

客户端代码:

double total = 0.0d;
private void btn_Ok(object sender, EventArgs e)
{
    CashContext cc = null;
    switch(cbxType.SelectedItem.ToString())
    {
        case "正常收费":
            cc = new CashContext(new CashNormal());
            break;
        case "满300送100":
            cc = new CashContext(new CashReturn("300", "100"));
            break;
        case "打8折":
            cc = new CashContext(new CashRebate("0.8"));
            break;
    }
    double totalPrices = 0d;
    totalPrices = cc.GetResult(Convert.ToDouble(txtPrice .Text) * Convert.ToDouble(txtNum.Text));
    total = total + totalPrices;
    lbxList.Items.Add("单价:"+ txtPrice.Text + "数量:" txtNum.Text + "" + cbxType.SelectedItem + "合计:" + totalPrices.ToString());
    lblResult.Text = total.ToString();
}

这样好像又是在客户端去判断使用哪一个算法了。怎么从客户端移走呢?

策略与简单工厂结合:
改造后的CashContext:

class CashContext
{
    CashSuper cs = null;

    public CashContext(string type)
    {
        switch(type)
        {
            case "正常收费":
                CashNormal cs0 = new CashNormal();
                cs = cs0;
                break;
            case "满300送100":
                CashReturn cr1 = new CashReturn("300", "100");
                cs = cr1;
                break;
            case "打8折":
                CashRebate cr2 = new CashRebate("0.8");
                cs = cr2;
                break;
        }
    }
}

客户端代码:

double total = 0.0d;
private void btnOk_Click(object sender, EventArgs e)
{
    CashContext csuper = new CashContext(cbxType.SelectedItem.ToString());
    double totalPrices = 0d;
    totalPrices = cc.GetResult(Convert.ToDouble(txtPrice .Text) * Convert.ToDouble(txtNum.Text));
    total = total + totalPrices;
    lbxList.Items.Add("单价:"+ txtPrice.Text + "数量:" txtNum.Text + "" + cbxType.SelectedItem + "合计:" + totalPrices.ToString());
    lblResult.Text = total.ToString();
}

解析:策略模式是一种定义一系列算法的方法,从概念上看,所有这些算法都是完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。

策略模式的Strategy类层次为Context定义了一系列的可供重用的算法或行为。继承有助于析取出这些算法中的公共功能,另外一个优点就是简化了单元测试,因为每个算法都有 自己的类,可以通过自己的接口单独测试。

当不同的行为堆砌在一个类中时,就很难避免使用条件语句来选择合适的行为。将这些行为封装在一个个独立的Strategy类中,可以在使用这些行为的类中消除条件语句。

策略模式是用来封装算法的,但在实践中,我们发现可以用它来封装几乎任何类型的规则,只要在分析过程中听到需要在不同时间应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性。

在基本的策略模式中,选择所用具体实现的职责由客户端对象承担,并转给策略模式的Context对象。

你可能感兴趣的:(设计模式,策略模式)