【设计模式】策略模式——以商场促销为例

本文内容参考自《大话设计模式》(程杰 著)

注:以下代码为java实现

版本1

需求:

做一个商场收银软件,营业员根据客户所购买商品的单价和数量,向客户收费。

关键代码:

public class Cash
{
    private double  total   = 0;

    public void submit(int num, double price)
    {
        double totalPrices = num * price;
        total += totalPrices;

        System.out.println("单价:" + price + " 数量:" + num + "合计:" + totalPrices);
    }

    public double getTotal()
    {
        return total;
    }

    public void setTotal(double total)
    {
        this.total = total;
    }
}

版本2

需求:

增加打折功能

思路1:修改代码,比如打7折,则total *= 0.7;
评价:如果取消打折,或者修改折扣,需要频繁修改代码,不推荐。

思路2:增加折扣选项,关键代码如下:

public class Cash
{
    private double  total           = 0;
    private int     selectedIndex   = 0;

    public void selectFormLoad()
    {
        String[] selectForm = { "正常收费", "打8折", "打7折", "打5折" };
        selectedIndex = 0;
    }

    public void submit(int num, double price)
    {
        double totalPrices = 0;
        switch (selectedIndex)
        {
            case 0:
                totalPrices = num * price;
                break;
            case 1:
                totalPrices = num * price * 0.8;
                break;
            case 2:
                totalPrices = num * price * 0.7;
                break;
            case 3:
                totalPrices = num * price * 0.5;
                break;
        }
        total += totalPrices;
        System.out.println("单价:" + price + " 数量:" + num + "合计:" + totalPrices);
    }

    public double getTotal()
    {
        return total;
    }

    public void setTotal(double total)
    {
        this.total = total;
    }

    public int getSelectedIndex()
    {
        return selectedIndex;
    }

    public void setSelectedIndex(int selectedIndex)
    {
        this.selectedIndex = selectedIndex;
    }
}

问题:重复的代码太多,而且选项少,可变性不高!

版本3

需求:

可以灵活修改折扣,并且可以返利

//现金收费接口
public interface CashSuper
{
    public double acceptCash(double money);
}
//正常收费子类
public class CashNormal implements CashSuper
{

    public double acceptCash(double money)
    {
        return money;
    }
}
//打折收费子类
public class CashRebate implements CashSuper
{
    private double  moneyRebate = 1;

    public CashRebate(double moneyRebate)
    {
        this.moneyRebate = moneyRebate;
    }

    public double acceptCash(double money)
    {
        return money * moneyRebate;
    }
}
//返利收费子类
public class CashReturn implements CashSuper
{
    private double  moneyCondition  = 0;
    private double  moneyReturn     = 0;

    public CashReturn(double moneyCondition, double moneyReturn)
    {
        this.moneyCondition = moneyCondition;
        this.moneyReturn = moneyReturn;
    }

    public double acceptCash(double money)
    {
        double result = money;
        if (money >= moneyCondition)
        {
            result = money - money / moneyCondition * moneyReturn;
        }
        return result;
    }
}
//现金收费工厂类
public class CashFactory
{
    public static CashSuper createCash(String type)
    {
        CashSuper cs = null;
        if ("正常收费".equals(type))
        {
            cs = new CashNormal();
        }
        else if ("满300返100".equals(type))
        {
            cs = new CashReturn(300, 100);
        }
        else if ("打8折".equals(type))
        {
            cs = new CashRebate(0.8);
        }

        return cs;
    }
}
//客户端代码
public class Main
{
    private static double   total   = 0;

    public static void main(String[] args)
    {
        consume("正常收费", 1, 1000);
        consume("满300返100", 1, 1000);
        consume("打8折", 1, 1000);

        System.out.println("总计:" + total);
    }

    public static void consume(String type, int num, double price)
    {
        CashSuper csuper = CashFactory.createCash(type);
        double totalPrices = 0;
        totalPrices = csuper.acceptCash(num * price);
        total += totalPrices;
        System.out.println("单价:" + price + " 数量:" + num + "合计:" + totalPrices);
    }
}

以上代码进行了抽象封装,并使用了简单的工厂类,灵活性高了很多。

问题:简单工厂模式只是解决对象的创建问题,而且由于工厂本身包括了所有的收费方式,商场是可能经常性地更改打折额度和返利额度的,每次维护或扩展收费方式都要改动这个工厂,以致代码需要重新编译部署,这是非常糟糕的处理方式,所以用它不是最好的办法。面对算法的时常变动,我们可以使用策略模式!

版本4

需求:

可以经常性地更改打折额度和返利额度,而且要维护成本较低。

策略模式(Strategy):

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

商场收银时,如何促销,用打折还是返利,其实都是一些算法,最重要的是这些算法是随时都可能互相替换的,就这点变化,而封装变化点是我们面向对象的一种很重要的思维方式。我们来看看策略模式的结构图和基本代码:


【设计模式】策略模式——以商场促销为例_第1张图片

//Strategy类,定义所有支持的算法的公共接口
public interface Strategy
{
    public void algorithmInterface();
}
//ConcreteStrategy封装了具体的算法或行为,继承于Strategy
public class ConcreteStrategyA implements Strategy
{
    public void algorithmInterface()
    {
        System.out.println("算法A实现");
    }
}
public class ConcreteStrategyB implements Strategy
{
    public void algorithmInterface()
    {
        System.out.println("算法A实现");
    }
}
public class ConcreteStrategyC implements Strategy
{
    public void algorithmInterface()
    {
        System.out.println("算法C实现");
    }
}
//Context用一个ConcreteStrategy来配置,维护一个对Strategy对象的引用
public class Context
{
    private Strategy    strategy;

    public Context(Strategy strategy)
    {
        this.strategy = strategy;
    }

    public void contextInterface()
    {
        strategy.algorithmInterface();
    }
}
//客户端代码
public class Main
{
    public static void main(String[] args)
    {
        Context context;
        context = new Context(new ConcreteStrategyA());
        context.contextInterface();

        context = new Context(new ConcreteStrategyB());
        context.contextInterface();

        context = new Context(new ConcreteStrategyC());
        context.contextInterface();
    }
}

所以我们的可以进行以下修改:


【设计模式】策略模式——以商场促销为例_第2张图片

//CashContext类
public class CashContext
{
    CashSuper   cashSuper;

    public CashContext(CashSuper cashSuper)
    {
        this.cashSuper = cashSuper;
    }

    public double acceptCash(double money)
    {
        return cashSuper.acceptCash(money);
    }
}
//客户端代码
public class Main
{
    private static double   total   = 0;

    public static void main(String[] args)
    {
        consume("正常收费", 1, 1000);
        consume("满300返100", 1, 1000);
        consume("打8折", 1, 1000);

        System.out.println("总计:" + total);
    }

    public static void consume(String type, int num, double price)
    {
        CashContext cashContext = null;

        if ("正常收费".equals(type))
        {
            cashContext = new CashContext(new CashNormal());
        }
        else if ("满300返100".equals(type))
        {
            cashContext = new CashContext(new CashReturn(300, 100));
        }
        else if ("打8折".equals(type))
        {
            cashContext = new CashContext(new CashRebate(0.8));
        }

        double totalPrices = cashContext.acceptCash(num * price);
        total += totalPrices;

        System.out.println("单价:" + price + " 数量:" + num + "合计:" + totalPrices);
    }
}

问题:缺乏工厂模式的优势,在客户端要进行判断。

版本5

将策略模式与简单工厂模式结合起来:

//改造后的CashContext
public class CashContext
{
    CashSuper   cashSuper;

    public CashContext(CashSuper cashSuper)
    {
        this.cashSuper = cashSuper;
    }

    public CashContext(String type)
    {
        if ("正常收费".equals(type))
        {
            cashSuper = new CashNormal();
        }
        else if ("满300返100".equals(type))
        {
            cashSuper = new CashReturn(300, 100);
        }
        else if ("打8折".equals(type))
        {
            cashSuper = new CashRebate(0.8);
        }
    }

    public double acceptCash(double money)
    {
        return cashSuper.acceptCash(money);
    }
}
//客户端代码
public class Main
{
    private static double   total   = 0;

    public static void main(String[] args)
    {
        consume("正常收费", 1, 1000);
        consume("满300返100", 1, 1000);
        consume("打8折", 1, 1000);

        System.out.println("总计:" + total);
    }

    public static void consume(String type, int num, double price)
    {
        CashContext cashContext = new CashContext(type);

        double totalPrices = cashContext.acceptCash(num * price);
        total += totalPrices;

        System.out.println("单价:" + price + " 数量:" + num + "合计:" + totalPrices);
    }
}

简单工厂 和 策略模式+简单工厂 的对比

客户端代码:

//简单工厂模式的用法
CashSuper csuper = CashFactory.createCash(type);
...
totalPrices = csuper.acceptCash(num * price);

//策略模式与简单工厂模式结合的用法
CashContext cashContext = new CashContext(type);
double totalPrices = cashContext.acceptCash(num * price);

简单工厂模式需要让客户端认识两个类,CashSuper和CashFactory,而策略模式与简单工厂模式结合的用法,客户端就只需要认识一个类CashContext。耦合度更加降低。

我们在客户端实例化的是CashContext的对象,调用的是CashContext的方法,这使得具体的收费算法彻底地与客户端分离。连算法的父类CashSuper都不让客户端认识了。相当于创建了一个句柄类。

总结

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

另外,它简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试。

遗留问题:如果我们需要增加一种算法,比如满200返50,你就必须要改CashContext中的if或switch代码,有没有更低的维护成本?
(使用反射)

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