现在出现了一个需求,编写一个超市收银台软件,可以根据商品的价格统计出消费者应付金额,先来没头脑的一顿操作:
import java.util.Scanner;
public class Main {
private static double total = 0;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
do {
System.out.println("请输入商品价格:");
double price = scanner.nextDouble();
System.out.println("请输入商品数量:");
int count = scanner.nextInt();
total += price * count;
System.out.println("单价:"+price+",数量:"+count+",合计:"+price*count);
System.out.println("现在总价为"+total);
System.out.println("还要计算吗? 1继续,0退出");
} while (scanner.nextInt() == 1);
System.out.println("程序已退出");
}
}
运行示例:
请输入商品价格:
1.25请输入商品数量:
3
单价:1.25,数量:3,合计:3.75
现在总价为3.75
还要计算吗? 1继续,0退出
1
请输入商品价格:
2
请输入商品数量:
3
单价:2.0,数量:3,合计:6.0
现在总价为9.75
还要计算吗? 1继续,0退出
0
程序已退出
简单暴力地完成了任务!突然需求有改,需要增加一个可以为某件商品活动打折的功能,可以再无头脑的干一波:
import java.util.Scanner;
public class Main {
private static double total = 0;
private static String[] cashRates = {"正常", "1折", "2折", "3折", "4折", "5折", "6折", "7折", "8折", "9折", "正常"};
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
do {
System.out.println("请输入商品价格:");
double price = scanner.nextDouble();
System.out.println("请输入商品数量:");
int count = scanner.nextInt();
System.out.println("请输入该商品折扣(0正常,9为9折,8为八折,依次类推):");
int cashRate = scanner.nextInt();
cashRate = cashRate == 0 ? 10 : cashRate;
double sum = price * count * cashRate / 10;
total += sum;
System.out.println("折扣:"+cashRates[cashRate]+",单价:"+price+",数量:"+count+",合计:"+sum+",现在总价为"+total);
System.out.println("还要计算吗? 1继续,0退出");
} while (scanner.nextInt() == 1);
System.out.println("程序已退出");
}
}
运行示例(小数点问题先忽略,主要是为了说策略模式):
请输入商品价格:
1.25
请输入商品数量:
3
请输入该商品折扣(0正常,9为9折,8为八折,依次类推):
8
折扣:8折,单价:1.25,数量:3,合计:3.0,现在总价为3.0
还要计算吗? 1继续,0退出
1
请输入商品价格:
2.3
请输入商品数量:
3
请输入该商品折扣(0正常,9为9折,8为八折,依次类推):
9
折扣:9折,单价:2.3,数量:3,合计:6.209999999999999,现在总价为9.209999999999999
还要计算吗? 1继续,0退出
0
程序已退出
需求又要改了,又要添加一个现金返利活动(像满100返现50的活动),面对频繁修改的需求,就不适合按上面的步骤来改了,于是可以考虑换成上一章节的简单工厂模式来实现一下:
import java.util.Scanner;
public class Main {
private static double total = 0;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
do {
System.out.println("请输入该商品活动(normal/rate/benefit):");
String type = scanner.next();
System.out.println("输入活动参数param_1:");
//这里输入两个参数normal可以随便输,rate要输入第一个参数也就是折扣,benefit分别输入返利要求金额和返利金额大小
double param_1 = scanner.nextDouble();
System.out.println("输入活动参数param_2:");
double param_2 = scanner.nextDouble();
Cash cash = CashFactory.createCash(type, param_1, param_2);
System.out.println("请输入商品价格:");
double price = scanner.nextDouble();
System.out.println("请输入商品数量:");
int count = scanner.nextInt();
double sum = cash.acceptCash(price, count);
total += sum;
System.out.println("优惠类型:" + cash.getClass().getSimpleName() + ",单价:" + price + ",数量:" + count + ",合计:" + sum + ",现在总价为" + total);
System.out.println("还要计算吗? 1继续,0退出");
} while (scanner.nextInt() == 1);
System.out.println("程序已退出");
}
}
//抽象的收费手段
abstract class Cash {
public abstract double acceptCash(double price, int count);
}
//普通收费手段
class NormalCash extends Cash {
@Override
public double acceptCash(double price, int count) {
return price * count;
}
}
//打折收费手段
class RateCash extends Cash {
private double rate;
public RateCash(double rate){
this.rate = rate;
}
@Override
public double acceptCash(double price, int count) {
return price * count * rate;
}
}
//返现收费手段
class BenefitCash extends Cash {
private double moneyCondition;
private double moneyReturn;
public BenefitCash(double moneyCondition, double moneyReturn){
this.moneyCondition = moneyCondition;
this.moneyReturn = moneyReturn;
}
@Override
public double acceptCash(double price, int count) {
if ( (price * count) / moneyCondition > 1 )
return price * count - (int)((price * count) / moneyCondition) * moneyReturn;
return price * count;
}
}
//收费手段类工厂(不要看成现钞工厂( ⊙ o ⊙ ))
class CashFactory {
public static Cash createCash(String cashType, double... params){
Cash cash = null;
switch (cashType) {
case "normal":
cash = new NormalCash();
break;
case "rate":
cash = new RateCash((params[0]));
break;
case "benefit":
cash = new BenefitCash(params[0], params[1]);
break;
default:
System.out.println("找不到该优惠活动");
}
return cash;
}
}
运行示例:
请输入该商品活动(normal/rate/benefit):
benefit
输入活动参数param_1:
200
输入活动参数param_2:
50
请输入商品价格:
250
请输入商品数量:
1
优惠类型:BenefitCash,单价:250.0,数量:1,合计:200.0,现在总价为200.0
还要计算吗? 1继续,0退出
1
请输入该商品活动(normal/rate/benefit):
rate
输入活动参数param_1:
0.8
输入活动参数param_2:
0
请输入商品价格:
256
请输入商品数量:
2
优惠类型:RateCash,单价:256.0,数量:2,合计:409.6,现在总价为609.6
还要计算吗? 1继续,0退出
0
程序已退出
简单工厂模式较好地解决了需求频繁更改的情况,但某种算法改动时还是需要改动对应的工厂代码,有没有一种更优雅的方法呢,即使其更松耦合更易维护?该是策略模式出场的时候了:
先写业务逻辑层
//上下文,用来维护一个收费策略的引用
class CashContext {
private Cash cash;
public CashContext(Cash cash){
this.cash = cash;
}
public double acceptCash(double price, int count){
return cash.acceptCash(price, count);
}
}
//抽象的收费手段
abstract class Cash {
public abstract double acceptCash(double price, int count);
}
//普通收费手段
class NormalCash extends Cash {
@Override
public double acceptCash(double price, int count) {
return price * count;
}
}
//打折收费手段
class RateCash extends Cash {
private double rate;
public RateCash(double rate){
this.rate = rate;
}
@Override
public double acceptCash(double price, int count) {
return price * count * rate;
}
}
//返现收费手段
class BenefitCash extends Cash {
private double moneyCondition;
private double moneyReturn;
public BenefitCash(double moneyCondition, double moneyReturn){
this.moneyCondition = moneyCondition;
this.moneyReturn = moneyReturn;
}
@Override
public double acceptCash(double price, int count) {
if ( (price * count) / moneyCondition > 1 )
return price - (int)(price / moneyCondition) * moneyReturn;
return price;
}
}
上面代码通过用一个Context类来管理策略类,但是面临一个问题,这样写的话main方法又要和最初那样写一大串switch分支来解决策略的选择了,有一个比较好的方法就是通过策略+工厂模式,改良后的最终代码如下:
import java.util.Scanner;
public class Main {
private static double total = 0;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
do {
System.out.println("请输入该商品活动(normal/rate/benefit):");
String type = scanner.next();
System.out.println("输入活动参数param_1:");
//这里输入两个参数normal可以随便输,rate要输入第一个参数也就是折扣,benefit分别输入返利要求金额和返利金额大小
double param_1 = scanner.nextDouble();
System.out.println("输入活动参数param_2:");
double param_2 = scanner.nextDouble();
CashContext cashContext = new CashContext(type, param_1, param_2);
System.out.println("请输入商品价格:");
double price = scanner.nextDouble();
System.out.println("请输入商品数量:");
int count = scanner.nextInt();
double sum = cashContext.acceptCash(price, count);
total += sum;
System.out.println("优惠类型:" + cashContext.getCash().getClass().getSimpleName() + ",单价:" + price + ",数量:" + count + ",合计:" + sum + ",现在总价为" + total);
System.out.println("还要计算吗? 1继续,0退出");
} while (scanner.nextInt() == 1);
System.out.println("程序已退出");
}
}
//上下文,用来维护一个收费策略的引用
class CashContext {
private Cash cash;
public CashContext(String cashType, double... params){
switch (cashType) {
case "normal":
this.cash = new NormalCash();
break;
case "rate":
this.cash = new RateCash((params[0]));
break;
case "benefit":
this.cash = new BenefitCash(params[0], params[1]);
break;
default:
System.out.println("找不到该优惠活动");
}
}
public double acceptCash(double price, int count){
return cash.acceptCash(price, count);
}
public Cash getCash() {
return cash;
}
}
//抽象的收费手段
abstract class Cash {
public abstract double acceptCash(double price, int count);
}
//普通收费手段
class NormalCash extends Cash {
@Override
public double acceptCash(double price, int count) {
return price * count;
}
}
//打折收费手段
class RateCash extends Cash {
private double rate;
public RateCash(double rate){
this.rate = rate;
}
@Override
public double acceptCash(double price, int count) {
return price * count * rate;
}
}
//返现收费手段
class BenefitCash extends Cash {
private double moneyCondition;
private double moneyReturn;
public BenefitCash(double moneyCondition, double moneyReturn){
this.moneyCondition = moneyCondition;
this.moneyReturn = moneyReturn;
}
@Override
public double acceptCash(double price, int count) {
if ( (price * count) / moneyCondition > 1 )
return price * count - (int)((price * count) / moneyCondition) * moneyReturn;
return price * count;
}
}
这样,又将实例化策略的的具体过程由客户端转移到了Context类中
现在对比一下简单工厂模式和策略+工厂模式的代码:
工厂模式:
Cash cash = CashFactory.createCash(type, param_1, param_2);
策略+工厂模式:
CashContext cashContext = new CashContext(type, param_1, param_2);
主要区别就在此:
采用策略+工厂模式,main方法中只需认识CashContext就行了,连父类Cash都不需要认识。而纯工厂模式main需要知道Cash和CashFactory两个类。因此采用策略+工厂模式可以进一步实现解耦
总结:
策略模式是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同,策略模式可以以相同的方式调用所有的方法,从而减少了算法类和与使用算法类之间的耦合
另外,策略模式可以简化单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试
策略模式就是用来封装算法的,但在实践中,我们发现可以用它来封装几乎任何类型的规则,只要在分析过程中听到需要在不同时间应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性