前言
策略模式是设计模式中的行为型模式,所谓行为型就是其主要使用在方法有很大灵活性的情况。而之前的工厂模式主要是对创建对象的优化,减少程序中使用new对象的次数。策略模式在Java源码中也是很常见的,比如我们要比较两个对象的大小,既可以使用默认的Comparable接口,也可以实现自定义的比较规则,即实现Comparator接口。这两种比较比较方法都是不同比较规则的体现,属于不同的策略。策略模式从定义上是这么说的:定义了算法家族,把这些不同的算法封装起来,让他们之间可以相互替换。从而使得算法的替换不会影响调用者的变化。光从字面上感觉还不是特别清晰,简单来说就是要办成一件事可以有不同的方法,这些方法都是属于一个家族的,所以从本质上来讲,这些方法是没有区别的。因为外界调用的时候只需要知道这个家族的代表是谁就可以了,其他的调用者并不需要关心。
问题背景
实现一个收银软件,输入单价与数量计算总价格。商场的收费方式可能有多种而且还会随时改变。
编码实践一,使用简单工厂模式实现
//SimpleFactory Mode
public abstract class Cash{
public abstract double calculateRealCash(double money);
}
public class NormalCash extends Cash{
public double calculateRealCash(double money){
return money;
}
}
public class DiscountCash extends Cash{
//打折点
private double discountPoint;
public DiscountCash(double discountPoint){
this.discountPoint = discountPoint;
}
public double getDiscountPoint(){
return discountPoint;
}
public void setDiscountPoint(double discountPoint){
this.discountPoint = discountPoint;
}
public double calculateRealCash(double money){
return Math.floor(money * discountPoint);
}
}
public class RebateCash extends Cash{
private String rebateCondition;
private String moneyOfRebate;
public RebateCash(String rebateCondition,String moneyOfRebate){
this.rebateCondition = rebateCondition;
this.moneyOfRebate = moneyOfRebate;
}
public String getRebateCOndition(){return rebateCondition;}
public void setRebateCondition(String rebateCondition){this.rebateCondition = rebateCondition;}
public String getMoneyOfReabte(){return moneyOfRebate;}
public void setMoneyOfReabte(String moneyOfRebate){this.moneyOfRebate = moneyOfRebate;}
public double calculateRealCash(double money){
return money > Double.valueOf(rebateCondition) ? money - Math.floor(money * Double.valueOf(moneyOfRebate) / Double.valueOf(rebateCondition)) : money;
}
}
public class CashFactory{
public static Cash createCashObject(int type){
Cash cash = null;;
switch(type){
case 1:
cash = new NormalCash();
break;
case 2:
cash = new DiscountCash(0.85);
break;
case 3:
cash = new RebateCash("300","100");
break;
default:
cash = new NormalCash();
break;
}
return cash;
}
}
public class Test{
public static void main(String[] args){
Scanner in = new Scanner(System.in);
System.out.println("price:");
double price = in.nextDouble();
System.out.println("num:");
int num = in.nextInt();
double price2 = num * price;
System.out.println("Choose type to calculate the total price:\n"
+ " 1 is normal cash\n 2 is 80% discount cash\n 3 is full 300 return 100");
int type = in.nextInt();
Cash cash = CashFactory.createCashObject(type);
System.out.println("Total price is " + totalPrice);
}
}
测试结果:
编码实践二,使用策略模式实现
public class CashContext{
private Cash cash;
public CashContext(int type){
switch(type){
case 1:
cash = sm.new NormalCash();
break;
case 2:
cash = sm.new DiscountCash(0.85);
break;
case 3:
cash = sm.new RebateCash("300","100");
break;
}
}
public double getRealMoney(double money){
return cash.calculateRealCash(money);
}
}
//测试
public class Test{
public static void main(String[] args){
Scanner in = new Scanner(System.in);
System.out.println("price:");
double price = in.nextDouble();
System.out.println("num:");
int num = in.nextInt();
double price2 = num * price;
System.out.println("Choose type to calculate the total price:\n"
+ " 1 is normal cash\n 2 is 80% discount cash\n 3 is full 300 return 100");
int type = in.nextInt();
CashContext cashContext = new CashContext(type);
double totalPrice = cashContext.getRealMoney(price2);
System.out.println("Total price is " + totalPrice);
}
}
测试结果2:
OK,现在使用两种方法的测试结果是一致的,那么来看看两种设计模式的区别。对比类CashContext
与CashFactory
我们可以发现,策略模式封装了一个Cash对象,而这个对象是所有收费方法类的超类,那么在计算最后的钱数的时候,CashContext只是创建了一个实例,使用了一个CashContext类,而使用简单工厂模式在计算的时候,使用了CashFactory和Cash两个类,那么使用类的个数的多少又有什么关系呢?在Java中提倡依赖倒转原则,意思就是要要面向抽象编程而不是面向细节编程。延伸来讲就是,对外暴露的细节越少越好,因为对外暴露的细节越少,因需求变更而修改的代码越少,同时这也是开放-封闭原则的体现,所以从这点讲简单工厂模式对外暴露的字节大于策略模式,所以在面向抽象编程上更胜一筹。当然策略模式也有其不足,比如在构造函数中添加了逻辑判断语句,,与使用构造函数的初衷不是很符合,所以创建一个单独的方法或者类来单独完成类实例化的工作会更好一些。注意到,前面的CashFactory类的createCashObject()方法正是用来创建Cash对象的,所以可以把CashContext构造函数创建Cash对象的方法直接改为createCashObject()方法。这样每一个类的调用关系又进一步解耦了。
最后,对策略模式做一个简单的总结:
- 每个算法类封装了不同的实现,但完成的是相同的工作。这样就把算法实现类与使用算法类的类解耦
- 简化了单元测试
- 对外部暴露了更少的实现细节,符合开放-封闭原则
- 当算法实现类不断增加的时候,在Context类中增加的switch分支会越来越多