问题场景:
本文中所有代码收录在gitee仓库中,仓库地址: 点我我是仓库地址有需要的小伙伴可以自取
做一个商场收银软件,营业员根据客户所购买商品的单价和数量,向客户收费
思考:
相信大家一听到这个问题的时候应该已经想到了解决方案了,用两个文本框来输入单价和数量,一个’确定’按钮来算出每种商品的费用,用个列表框来记录商品的清单,一个标签来记录总计,ok话不多少,上代码。
run01类:
public static void main(String[] args) {
double price = 0d; //商品单价
int num = 0; //商品购买数量
double totalPrices = 0d; //当前商品合计费用
double total = 0d; //总计所有商品费用
int discount = 1; //商品销售模式
Scanner sc = new Scanner(System.in);
do {
System.out.println("请输入商品销售模式 1.原价 2.八折 3.七折");
discount = Integer.parseInt(sc.nextLine());
System.out.println("请出入商品单价:");
price = Double.parseDouble(sc.nextLine());
System.out.println("请输入商品数量:");
num = Integer.parseInt(sc.nextLine());
System.out.println();
if(price>0 && num>0){
//通过单价*数量获得当前商品合计费用
//通过累加获得总价费用
totalPrices = price * num * discount;
total = total + totalPrices;
System.out.println();
System.out.println("销售模式为:" + discount + "单价:" + price + "元 数量:" + num + "合计:" + totalPrices + "元");
System.out.println();
System.out.println("总计:" + total + "元");
System.out.println();
}
}while (price>0 && num>0);
}
ok,相信大家刚开始想到的结局方案应该都应该差不多,但是现在有一个新问题,这一段代码出现了一些问题
- 代码不够简洁。
- 维护和扩展性不高。
- 如果想新加一个促销模式就不是很方便了。
此时相信有小伙伴应该会跟我这样写:
run02类:
public static void main(String[] args) {
double price = 0d; //商品单价
int num = 0; //商品购买数量
double totalPrices = 0d; //当前商品合计费用
double total = 0d; //总计所有商品费用
int discount = 1; //商品销售模式
Scanner sc = new Scanner(System.in);
do {
System.out.println("请输入商品销售模式 1.原价 2.八折 3.七折");
discount = Integer.parseInt(sc.nextLine());
System.out.println("请出入商品单价:");
price = Double.parseDouble(sc.nextLine());
System.out.println("请输入商品数量:");
num = Integer.parseInt(sc.nextLine());
System.out.println();
if (price > 0 && num > 0) {
switch (discount) {
case 1:
totalPrices = price * num; //正常收费
break;
case 2:
totalPrices = price * num * 0.8; //打八折
break;
case 3:
totalPrices = price * num * 0.7; //打七折
break;
}
total = total + totalPrices;
System.out.println();
System.out.println("销售模式为:" + discount + "单价:" + price + "元 数量:" + num + "合计:" + totalPrices + "元");
System.out.println();
System.out.println("总计:" + total + "元");
System.out.println();
}
} while (price > 0 && num > 0);
}
如果需要别的折扣就再加一个逻辑就行了。
再思考:
这样所说可以解决问题,简单新增折扣倒是还好,如果想要新增稍微复杂的方式就不行,例如满七百减两百,还有积分兑换的促销活动。如果继续在switch块中继续写就不好了。那应该怎么办呢。
- 我们前面已经学过了简单工厂模式,这个是不是也可以使用简单工厂模式呢。ok开干。
定义收费抽象类
public abstract class CashSuper {
//定义收费的抽象方法,参数为单价和数量
public abstract double acceptCash(double price,int num);
}
定义正常收费类
public class CashNormal extends CashSuper{
@Override
public double acceptCash(double price, int num) {
//原价返回
return price * num;
}
}
定义打折折扣类
public class CashRebate extends CashSuper{
//打折收费
private double moneyRebate = 1d;
//初始化必须输入折扣率。八折就输入0.8
public CashRebate(double moneyRebate){
this.moneyRebate = moneyRebate;
}
@Override
public double acceptCash(double price, int num) {
//计算收费时需要在原价基础上乘以折扣率
return price * num + this.moneyRebate;
}
}
定义积分返回类
public class CashReturn extends CashSuper{
private double moneyCondition = 0d; //返利条件
private double moneyReturn = 0d; //返利值
//返利收费。初始化时需要输入返利条件和返利值。
//比如“满300返100”,就是moneyCondition=300,moneyReturn=100
public CashReturn(double moneyCondition,double moneyReturn){
this.moneyCondition = moneyCondition;
this.moneyReturn = moneyReturn;
}
//收费计算时,当达到返利条件,就原价减去返利值
@Override
public double acceptCash(double price, int num) {
double result = price * num;
if(moneyCondition>0 && result>=moneyCondition){
result = result - Math.floor(result / moneyCondition) * moneyReturn;
}
return result;
}
}
客户端代码类
public static void main(String[] args) {
double price = 0d; //商品单价
int num = 0; //商品购买数量
double totalPrices = 0d; //当前商品合计费用
double total = 0d; //总计所有商品费用
int discount = 1; //商品销售模式
Scanner sc = new Scanner(System.in);
do {
System.out.println("请输入商品销售模式 1.原价 2.八折 3.七折");
discount = Integer.parseInt(sc.nextLine());
System.out.println("请出入商品单价:");
price = Double.parseDouble(sc.nextLine());
System.out.println("请输入商品数量:");
num = Integer.parseInt(sc.nextLine());
System.out.println();
if (price > 0 && num > 0) {
//简单工厂模式根据discount的数字选择合适的收费类生成实例
CashSuper csuper = CashFactory.createCashAccept(discount);
//通过多态,可以根据不同收费策略计算得到收费的结果
totalPrices = csuper.acceptCash(price,num);
total = total + totalPrices;
System.out.println();
System.out.println("销售模式为:" + discount + "单价:" + price + "元 数量:" + num + "合计:" + totalPrices + "元");
System.out.println();
System.out.println("总计:" + total + "元");
System.out.println();
}
} while (price > 0 && num > 0);
}
这就是利用之前学习的简单工厂模式实现了业务,将各个业务之间分离开,互不影响。这样无论是修改还是新增新的模式都可以直接操作,不会影响到别的类方法。
思考:
如果此时再新增一种促销模式满100积分10点,以后积分到一定时候可以领取奖品,应该实现呢。
- 定义新的促销模式类
- 新的促销模式类继承收费抽象类
- 工厂新增促销模式判断
这样可以实现以上新增的促销模式,但是我们想一下,我们需要修改的类就太多了,商场的促销模式有很多,我们不可能每次有一个新的促销模式就想类似新增,这样不太理想。
所以我们可以使用今日的主题了,策略模式
策略模式(Strategy):它定义了算法家族,分别封装起来,让它们
之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。
商场收银时如何促销,用打折还是返利,其实都是一些算法,用工厂来生成算法对象,这没有错,但算法本身只是一种策略,最重要的是这些算法是随时都可能互相替换的,这就是变化点,而封装变化点是我们面向对象的一种很重要的思维方式
策略模式实现
新增CashContext类:
public class CashContext {
private CashSuper cs; //声明一个CashSuper对象
//通过构造方法,传入具体的收费策略
public CashContext(CashSuper cashSuper){
this.cs = cashSuper;
}
public double getResult(double price,int num){
//根据收费策略的不同,获得计算结果
return this.cs.acceptCash(price,num);
}
}
客户端相应修改
public static void main(String[] args) {
double price = 0d; //商品单价
int num = 0; //商品购买数量
double totalPrices = 0d; //当前商品合计费用
double total = 0d; //总计所有商品费用
int discount = 1; //商品销售模式
Scanner sc = new Scanner(System.in);
do {
System.out.println("请输入商品销售模式 1.原价 2.八折 3.七折");
discount = Integer.parseInt(sc.nextLine());
System.out.println("请出入商品单价:");
price = Double.parseDouble(sc.nextLine());
System.out.println("请输入商品数量:");
num = Integer.parseInt(sc.nextLine());
System.out.println();
if (price > 0 && num > 0) {
CashContext cc = null;
//根据用户输入,将对应的策略对象作为参数传入CashContext对象中
switch (discount){
case 1:
cc = new CashContext(new CashNormal());
case 2:
cc = new CashContext(new CashRebate(0.8d));
case 3:
cc = new CashContext(new CashRebate(0.7d));
case 4:
cc = new CashContext(new CashReturn(300d,100d));
}
//通过Context的getResult方法的调用,可以得到收取费用的结果
//让具体算法与客户进行隔离
totalPrices = cc.getResult(price,num);
total = total + totalPrices;
System.out.println();
System.out.println("销售模式为:" + discount + "单价:" + price + "元 数量:" + num + "合计:" + totalPrices + "元");
System.out.println();
System.out.println("总计:" + total + "元");
System.out.println();
}
} while (price > 0 && num > 0);
}
但是不难看出来现在又回到了在客户端判断代码逻辑了,所以我们要优化一下
策略与简单工厂结合
改造CashContext
public class CashContext {
private CashSuper cs; //声明一个CashSuper对象
//通过构造方法,传入具体的收费策略
public CashContext(int cashType){
switch (cashType){
case 1:
this.cs = new CashNormal();
break;
case 2:
this.cs = new CashRebate(0.8d);
break;
case 3:
this.cs = new CashRebate(0.7d);
break;
case 4:
this.cs = new CashReturn(300d, 100d);
break;
}
}
public double getResult(double price,int num){
//根据收费策略的不同,获得计算结果
return this.cs.acceptCash(price,num);
}
}
修改客户端
public static void main(String[] args) {
double price = 0d; //商品单价
int num = 0; //商品购买数量
double totalPrices = 0d; //当前商品合计费用
double total = 0d; //总计所有商品费用
int discount = 1; //商品销售模式
Scanner sc = new Scanner(System.in);
do {
System.out.println("请输入商品销售模式 1.原价 2.八折 3.七折");
discount = Integer.parseInt(sc.nextLine());
System.out.println("请出入商品单价:");
price = Double.parseDouble(sc.nextLine());
System.out.println("请输入商品数量:");
num = Integer.parseInt(sc.nextLine());
System.out.println();
if (price > 0 && num > 0) {
//根据用户输入,将对应的策略对象作为参数传入CashContext对象中
CashContext cc = new CashContext(discount);
//通过Context的getResult方法的调用,可以得到收取费用的结果
//让具体算法与客户进行隔离
totalPrices = cc.getResult(price,num);
total = total + totalPrices;
System.out.println();
System.out.println("销售模式为:" + discount + "单价:" + price + "元 数量:" + num + "合计:" + totalPrices + "元");
System.out.println();
System.out.println("总计:" + total + "元");
System.out.println();
}
} while (price > 0 && num > 0);
}
策略模式解析
策略模式是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合
总结
//简单工厂模式的用法
CashSuper csuper = CashFactory.createCashAccept(discount);
totalPrices = csuper.acceptCash(price,num);
//策略模式与简单工厂结合的用法
CashContext cc = new CashContext(discount);
totalPrices = cc.getResult(price,num);
简单工厂模式我需要让客户端认识两个类,CashSuper和CashFactory,而策略模式与简单工厂结合的用法,客户端就只需要认识一个类CashContext就可以了。耦合更加降低
本文中所有代码收录在gitee仓库中,仓库地址: 点我我是仓库地址有需要的小伙伴可以自取