大话设计模式-策略模式

策略模式

作者通过一个商场收费的例子来引入了策略模式,由于文中的例子基本是C#编写,因此自己仿照着课本的例子写了一遍JAVA代码,这里没设计界面,简单的用Scanner取代了界面输入。

面向过程实现商场收费

为了巩固简单工厂模式,作者在刚开始实现商场收费程序的时候仍然采用了面向过程的思路。具体的实现代码如下:

import java.util.ArrayList;
import java.util.Scanner;

/**
 * @Author yirui
 * @Date 2024/4/10 10:44
 * @Version 1.0
 */
public class AcceptCash {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        double totalPrice = 0d;
        try {
            ArrayList<String> priceList = new ArrayList<>();
            String acceptRule;//收费规则:单价,数量,优惠方式。逗号分隔
            double price;
            int rule,count;
            while (true){
                acceptRule = sc.nextLine();
                if (acceptRule.equals("end")){
                    break;
                }else {
                    priceList.add(acceptRule);
                    String[] list = acceptRule.split(",");
                    price = Double.parseDouble(list[0]);
                    count = Integer.parseInt(list[1]);
                    rule = Integer.parseInt(list[2]);
                    switch (rule){
                        case 1://正常收费
                            totalPrice += price*count;
                            break;
                        case 2://打八折
                            totalPrice += price*count*0.8;
                            break;
                        case 3://300-100
                            totalPrice += (price*count-(Math.floor(price*count/300)*100));
                            break;
                    }
                }

            }
            System.out.println(totalPrice);
            System.out.println(priceList);
        }catch (Exception e){
            System.out.println(e.toString());
        }finally {
            sc.close();
        }
    }
}

这里简单解释一下上面的代码,由于没有输入框,因此采用了Scanner来替代,控制台输入的标准格式是:单价,数量,规则。当输入end表示输入结束。计算逻辑也很简单,只有中间switch那一小段代码块。整个代码的流程如下:先从输入中把单价,数量,规则分别解析出来;再根据规则选择不同的运算逻辑,将最终实收的价格加到总价里面然后输出。最后测试的运行结果如下图所示:
在这里插入图片描述

这是很典型的面向过程解题的思路。在这段代码中包含了界面逻辑(从控制台获取输入并解析)、规则判断逻辑和价格计算逻辑;当需求发生变化,都需要在这个类中进行相应的修改,当业务逻辑变复杂后,无论是扩展、维护和复用的角度,这段代码都没法满足

简单工厂模式实现商场收费

接下来让我们利用面向对象的思维来分析这个问题,并通过前面讲过的简单工厂模式来实现它:

  1. 首先在这个商场收费问题中,打折的收费规则我们可以把他抽象出一个收费父类CashSuper,父类中包含了所有收费规则应该有的方法(所有收费规则的最终目的都是计算出实收费用)。
/**
 * @Author yirui
 * @Date 2024/4/10 18:06
 * @Version 1.0
 */
public abstract class CashSuper {
    public abstract double acceptPrice(double price, int count);//计算实收价格
}
  1. 为不同收费方式(有的可能是打折,有的可能是满减等等)的规则创建收费子类:
/**
 * @Author yirui
 * @Date 2024/4/10 18:22
 * @Version 1.0
 */
public class CashRebate extends CashSuper {//打折收费方式

    private double robate;//打几折,这个属性是打折方式特有的,因此不在父类中定义。

    public CashRebate(double robate) {//在初始化的时候,一定要指定打几折
        this.robate = robate;
    }

    @Override
    public double acceptPrice(double price, int count) {//打折收费的计算规则
        return price * count * robate;
    }
}

public class CashReturn extends CashSuper {//满减收费方式
    private double montyCondition;
    private double moneyReturn;

    public CashReturn(double montyCondition, double moneyReturn) {//同样,在初始化的时候需要告知满减条件,满多少减多少。
        this.montyCondition = montyCondition;
        this.moneyReturn = moneyReturn;
    }

    @Override
    public double acceptPrice(double price, int count) {//满减收费的计算规则
        double normalMoney = price * count;
        double acceptMoney = normalMoney - moneyReturn * Math.floor(normalMoney / montyCondition);
        return acceptMoney;
    }
}

public class CashNormal extends CashSuper {//正常收费方式
    @Override
    public double acceptPrice(double price, int count) {//不做活动的计算规则
        return price*count;
    }
}
  1. 创建一个工厂类来管理这些收费类的创建,它可以根据收营员选择的规则,来决定创建哪个类,怎么创建这个类。(这里为了方便,只制定了三种收费规则,1表示正常收费,2表示打八折,3表示满300-100)
/**
 * @Author yirui
 * @Date 2024/4/10 18:42
 * @Version 1.0
 */
public class CashFactory {
    public static CashSuper createCashAccept(int type){
        CashSuper cashSuper = null;
        switch (type){
            case 1://正常收费
                cashSuper = new CashNormal();
                break;
            case 2://打八折
                cashSuper = new CashRebate(0.8);
                break;
            case 3://300-100
                cashSuper = new CashReturn(300,100);
                break;
        }
        return cashSuper;
    }
}
  1. 修改主程序代码,最终实现商场收费功能
import java.util.ArrayList;
import java.util.Scanner;

/**
 * @Author yirui
 * @Date 2024/4/10 10:44
 * @Version 1.0
 */
public class AcceptCash1 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        double totalPrice = 0d;
        try {
            ArrayList<String> priceList = new ArrayList<>();
            String acceptRule;//收费规则:单价,数量,优惠方式。逗号分隔
            double price;
            int rule,count;
            while (true){
                acceptRule = sc.nextLine();
                if (acceptRule.equals("end")){
                    break;
                }else {
                    priceList.add(acceptRule);
                    String[] list = acceptRule.split(",");
                    price = Double.parseDouble(list[0]);
                    count = Integer.parseInt(list[1]);
                    rule = Integer.parseInt(list[2]);
                    CashSuper cashAccept = CashFactory.createCashAccept(rule);//利用工厂创建对象
                    totalPrice += cashAccept.acceptPrice(price,count);//调用对象方法计算出实收价格,并将其加到总计账单。
                }

            }
            System.out.println(totalPrice);
            System.out.println(priceList);
        }catch (Exception e){
            System.out.println(e.toString());
        }finally {
            sc.close();
        }
    }
}

上述代码成功的将页面逻辑额和业务逻辑分类开,主程序负责接收数据并解析,工厂类负责判断收费规则创建出正确的子类,子类负责实际的运算规则。当出现了新的收费规则,或者原本的收费规则发生了变化时,只需要修改子类和工厂类。实际运行结果如下图,可以看到与面向过程的方式结果一样:
在这里插入图片描述
当然上面的代码还存在很多能够改进的地方,比如我在工厂类中写死了满减条件和打折倍率,这样每次这些东西发生变化时(比如我突然想满1000-100,或者我亏血大甩卖,全场一折等等),我都需要修改工厂类的内容。这点与作者在文中提出的一样:这个模式只是解决对象的创建问题,而且由于工厂本身包括了所有的收费方式,商场是可能经常性地更改打折额度和返利额度,每次维护或扩展收费方式都要改动这个工厂,以致代码需重新编译部署,这真的是很糟糕的处理方式,所以用它不是最好的办法。

策略模式实现商场收费

在刚看策略模式前面一点的时候,我这个菜鸟是有点懵逼的,这和简单工厂模式有什么区别?还是大佬的随口一句话,让我纠正了我的思维错误:我们前面写的CashSuper父类和它的子类,这种通过继承的方式来编程的方式,它和简单工厂模式有什么关系呢?继承只是面向对象编程的一种常用的方式,在很多设计模式种都会用到,它并不是简单工厂模式所特有的,在我们前面的代码中,真正体现简单工厂模式的只有那个工厂类。
策略模式在书中的定义是这样的:它定义了算法家族,分别封装起来,让他们之间可互相替换,此模式让算法的变化不会影响到使用算法的用户。
它的结构图如下:
在这里插入图片描述

如果把简单工厂模式比作生产车间的话,策略模式则类似于我们生活中的“小霸王”(小时候玩的一种游戏机),“小霸王”公司规定了插槽的形状,所有与“小霸王”合作的公司生产的游戏卡,无论他做的是什么游戏,最后所有的游戏卡一定有一个一样的插脚形状(我们给它起个名字叫做“小霸王”游戏卡),不然插不上去。当用户用“小霸王”游戏机打游戏的时候,给游戏机插上游戏卡就可以了,想玩什么游戏就插什么卡(前提是能插上去)。
在上面这个例子里,用来插卡的“小霸王”游戏机相当于Context,“小霸王”游戏卡相当于Strategy,“小霸王”公司规定的插槽的形状其实就相当于Strategy里面的公共接口,而他的子类就是具体的游戏卡,比如魂斗罗,坦克大战,雪人兄弟等等。(这些都是我自己的理解哈,有问题希望大佬及时指正。)
言归正传,文中尝试利用策略模式来实现商场收费。而之前编写的收费父类CashSuper其实就是Strategy,它的子类就是具体的实现。因此我们只需要自己编写一个Context。

/**
 * @Author yirui
 * @Date 2024/4/10 23:35
 * @Version 1.0
 */
public class CashContext {
    private CashSuper cashSuper;

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

    public double getResult(double price, int count) {
        return cashSuper.acceptPrice(price, count);
    }
}

写完这个类后,我们还需要重新编写主程序代码:

import java.util.ArrayList;
import java.util.Scanner;

/**
 * @Author yirui
 * @Date 2024/4/10 10:44
 * @Version 1.0
 */
public class AcceptCash2 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        double totalPrice = 0d;
        try {
            ArrayList<String> priceList = new ArrayList<>();
            String acceptRule;//收费规则:单价,数量,优惠方式。逗号分隔
            double price;
            int rule, count;
            while (true) {
                acceptRule = sc.nextLine();
                if (acceptRule.equals("end")) {
                    break;
                } else {
                    priceList.add(acceptRule);
                    String[] list = acceptRule.split(",");
                    price = Double.parseDouble(list[0]);
                    count = Integer.parseInt(list[1]);
                    rule = Integer.parseInt(list[2]);
                    CashContext cashContext = null;
                    switch (rule) {
                        case 1://正常收费
                            cashContext = new CashContext(new CashNormal());
                            break;
                        case 2://打八折
                            cashContext = new CashContext(new CashRebate(0.8));
                            break;
                        case 3://300-100
                            cashContext = new CashContext(new CashReturn(300,100));
                            break;
                    }
                    totalPrice += cashContext.getResult(price,count);
                }

            }
            System.out.println(totalPrice);
            System.out.println(priceList);
        } catch (Exception e) {
            System.out.println(e.toString());
        } finally {
            sc.close();
        }
    }
}

这样做相对于简单工厂模式而言有什么区别呢?
当打折规则发生变化时,我们的Context类不需要发生变化,我们需要做的事是在主函数中创建Context对象时,传入不同的CashSuper的子类。(换游戏卡,不换游戏机)。但是细心的同学可能已经发现了,这样做不是又回到了面向过程的老路了吗,虽然打折的计算逻辑分离出去了,但是界面逻辑和判断创建什么对象的业务逻辑又混到一起去了。没毛病,所以就有了后来的策略模式与简单工厂模式结合使用的例子!这也告诉我们,设计模式并不是相互独立非A即B的,它们可以混着用。我们下文介绍哈。在这里插入图片描述

  • 18
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值