美文网首页设计模式@IT·互联网程序员
设计模式学习(一)——策略模式

设计模式学习(一)——策略模式

作者: 活成理想中的样子 | 来源:发表于2017-05-19 19:29 被阅读166次
设计模式logo.jpg

一.需求

小王在家附近开了一家便利店。进货、上架……经过一番折腾,总算可以开始卖货了。

等等!至少还需要一个收银台吧?好在小王是程序员出身,这点小事难不倒他。很快,小王就开发了一套收银软件,可以正常进行收银了。附近的居民都来店里买东西,生意红红火火,小王的心里美滋滋的。

好景不长,附近又新开了一家更大的便利店,不少顾客都跑去那边买东西了,而且人家还有促销活动。

小王终于坐不住了,琢磨着自己的店也得搞点促销活动才行。他准备搞一次满100减10元的活动。这样一来,之前的软件就需要进行升级了,这点需求同样难不倒小王,说干就干。

二.初步尝试

之前的逻辑:

class Goods { // 商品
    private String name; // 商品名称
    private double number; // 数量
    private double price; // 单价

    public Goods(String name, double number, double price) {
        this.name = name;
        this.number = number;
        this.price = price;
    }
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getNumber() {
        return number;
    }

    public void setNumber(double number) {
        this.number = number;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }
}

class Charge { // 计费模块

    public double charge(List<Goods> goodsList) {
        double sum = 0;
        for (Goods goods : goodsList) {
            sum += goods.getPrice() * goods.getNumber();
        }
        return sum;
    }
}

class CheckStand { // 收银台
    public static void main(String[] args) {
        List<Goods> goodsList = new ArrayList<Goods>();
        goodsList.add(new Goods("可乐", 20, 3.5));
        goodsList.add(new Goods("牛奶", 30, 2.5));
        Charge charge = new Charge();
        printShoppingList(goodsList, charge.charge(goodsList));
    }

    // 打印购物清单
    private static void printShoppingList(List<Goods> goodsList, double sum) {
        System.out.println("您本次消费:" + sum + "元,购物清单如下:");
        for (Goods goods : goodsList) {
            System.out.println(goods.getName() + "\t" + goods.getNumber() + "*" + goods.getPrice());
        }
    }
}

显然,需要修改计费模块,将计费的公式进行修改。很快,小王就改好了:

public double charge(List<Goods> goodsList) {
        double sum = 0;
        for (Goods goods : goodsList) {
            sum += goods.getPrice() * goods.getNumber();
        }
        // 以下是新增的逻辑
        if (sum >= 100) {
            sum = sum - 10;
        }
        return sum;
    }

运行一下看看:


您本次消费:135.0元,购物清单如下:
可乐  20.0*3.5
牛奶  30.0*2.5

看起来目前一切都很顺利,来买东西的人络绎不绝,小王的生意又重新红火了起来。

但是,又出现了一个新的问题:店里的牛奶很多快要过期了,如果再不处理,恐怕亏损巨大!!!怎么办?小王决定甩卖,买一袋送一袋,如果参与买一送一活动,就不再参与之前的满减活动了。之前的程序还要再改:

public double charge(List<Goods> goodsList) {
        double sum = 0;
        boolean twoForOne = false;
        for (Goods goods : goodsList) {
            if (goods.getName().equals("牛奶")) { // 牛奶买一送一
                int num = (int)goods.getNumber() / 2; // 求出数量的一半
                if ((int)goods.getNumber() % 2 != 0) { // 如果是奇数个,还需要再加1
                    num += 1;
                }
                sum += goods.getPrice() * num;
                twoForOne = true;
            } else {
                sum += goods.getPrice() * goods.getNumber();
            }
        }
        if (twoForOne == false) { // 没有参与牛奶买一送一,才可以参与满减
            if (sum >= 100) {
                sum = sum - 10;
            }
        }
        return sum;
    }

再运行一次看看:

您本次消费:107.5元,购物清单如下:
可乐  20.0*3.5
牛奶  30.0*2.5

虽然可以运行,但是charge代码看起来越来越繁琐,如果过一段时间可乐也参与满一送一呢?如果想满200减30呢?每次都要修改charge的代码。

有没有更好的方案呢?小王陷入了深深的思索。

三.更好的方案

以上实现有两个主要问题:
1.经常需要修改的是进行计费的charge方法,一旦有新的优惠活动,就需要修改这里。我们都知道,程序应该对扩展开放,对修改关闭,显然,这里并不满足。

2.优惠活动可能会有很多,但活动种类其实是非常有限的,我们能不能抽象出常见的优惠活动呢?

显然,以上两个问题的答案都是:Yes!
1.我们可以实现不同类型的Charge类,针对不同的优惠活动只需使用不同的Charge类即可,当有新的优惠活动时,我们只需实现新的Charge类,老的无需修改。
2.我们可以开发出几种不同的优惠活动类,例如满m减n,买m送n。

来看我们的新代码:

/**
 * 定义营销活动
 */
enum Promotion {
    MONEY_OFF, // 满减
    PRESENTATION; // 买就送
}

class Goods {
    private String name; // 商品名称
    private double number; // 数量
    private double price; // 单价
    private Promotion promotion; // 参与哪种促销活动

    public Goods(String name, double number, double price, Promotion promotion) {
        this.name = name;
        this.number = number;
        this.price = price;
        this.promotion = promotion;
    }
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getNumber() {
        return number;
    }

    public void setNumber(double number) {
        this.number = number;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public Promotion getPromotion() {
        return promotion;
    }

    public void setPromotion(Promotion promotion) {
        this.promotion = promotion;
    }
}

/**
 * 抽象的计费类
 */
abstract class Charge {
    abstract  public double charge(List<Goods> goodsList);
}

/**
 * 满减营销活动计费
 */
class MoneyOffCharge extends Charge {

    private double threshold; // 满多少

    private double off; // 减多少

    public MoneyOffCharge(double threshold, double off) {
        this.threshold = threshold;
        this.off = off;
    }
    @Override
    public double charge(List<Goods> goodsList) {
        double sum = 0;
        double promotionSum = 0;// 活动商品总价
        for (Goods goods : goodsList) {
            if (goods.getPromotion() == Promotion.MONEY_OFF) { // 如果该商品参与满减活动
                promotionSum += goods.getNumber() * goods.getPrice();
            } else { // 不参与活动
                sum += goods.getNumber() * goods.getPrice();
            }
        }
        sum += promotionSum;
        if (promotionSum >= threshold) {
            sum = sum - off;
        }
        return sum;
    }
}

/**
 * 买就送营销活动计费
 */
class PresentationCharge extends Charge {
    private double number; // 买多少

    private double presentation; // 送多少

    public PresentationCharge(double number, double presentation) {
        this.number = number;
        this.presentation = presentation;
    }

    @Override
    public double charge(List<Goods> goodsList) {
        double sum = 0;
        for (Goods goods : goodsList) {
            if (goods.getPromotion() == Promotion.PRESENTATION) { // 如果该商品参与买就送活动
                if (goods.getNumber() >= number) { // 达到活动条件
                    // number + presentation 为一组,计算有多少组
                    int chargeGroup = (int)(goods.getNumber() / (number + presentation));
                    // 计算分组后还剩余多少个
                    double rest = goods.getNumber() % (number + presentation);
                    // 计算在rest中有多少个需要付费,取rest跟number之中较小的
                    double chargeRest = rest > number ? number : rest;
                    double chargeNumber = chargeGroup * number + chargeRest;

                    sum += chargeNumber * goods.getPrice();
                }
            } else { // 不参与活动
                sum += goods.getNumber() * goods.getPrice();
            }
        }
        return sum;
    }
}

class CheckStand {
    public static void main(String[] args) {
        List<Goods> goodsList = new ArrayList<Goods>();
        goodsList.add(new Goods("可乐", 20, 3.5, Promotion.MONEY_OFF));
        goodsList.add(new Goods("牛奶", 30, 2.5, Promotion.MONEY_OFF));
        Charge charge = new MoneyOffCharge(100, 10);
        printShoppingList(goodsList, charge.charge(goodsList));
    }

    private static void printShoppingList(List<Goods> goodsList, double sum) {
        System.out.println("您本次消费:" + sum + "元,购物清单如下:");
        for (Goods goods : goodsList) {
            System.out.println(goods.getName() + "\t" + goods.getNumber() + "*" + goods.getPrice());
        }
    }
}

运行一下:

您本次消费:135.0元,购物清单如下:
可乐  20.0*3.5
牛奶  30.0*2.5

这一次,小王的脸上终于露出了久违的笑容,再也不用担心因为促销活动要反复修改代码了。

四.模式总结

在上面的新方案中,我们其实已经使用了策略模式。
类图

策略模式类图

Context类:上下文,可以是不同的业务场景,在这个场景中会有一些处理步骤设计到多种处理方式。
Strategy接口:定义算法接口。
ConcreteStrategyA类:具体的算法实现类。

在我们上面的代码中,收银台CheckStand 就是Context类,Charge类就是Strategy接口(注意,接口并不意味着一定要使用interface,而是范围抽象的父类或接口),MoneyOffCharge和PresentationCharge就是具体的算法实现类。

使用场景
某个行为有多种算法实现,运行时需要灵活在多种算法间切换。

优点
1.可以在运行时动态改变行为方式,增加灵活性
2.将算法变化与客户端独立,增加维护性与可扩展性

缺点
客户端必须知道所有的策略类,并自行决定使用哪一个

参考资料:

《Head First设计模式》

本文已迁移至我的博客:http://ipenge.com/6863.html

相关文章

网友评论

  • f13913dafd44:n = n - number - presentation;这句话不是太好容易理解,能解释下吗?
    活成理想中的样子: @simple的向往 好的,没问题,我稍后更新一下代码
    f13913dafd44: @自由水鸟 这样就比较简单易懂啦,不知你能否把这个代码写出来!思路就是你把组数跟100件的余数求出来,再乘单价就是需要付费的金额!
    活成理想中的样子: @simple的向往 你好,我是这样考虑的,如果买了100件商品,活动是买5赠2,那么我的算法是想要计算出这100件中一共有多少是免费的,我采用循环的方式,每当买5件就会赠送2件,那么其实5+2=7个为一组,这7个中5个是需要付费的,2个是免费的,我需要计算出100里面一共有多少个这样的组。不知道我这样讲您是否明白,欢迎讨论哈

本文标题:设计模式学习(一)——策略模式

本文链接:https://www.haomeiwen.com/subject/idqjxxtx.html