定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。
例子:商场收银,营业员根据客户所购买商品的单价和数量,向客户收费,但有时候会有打折或者满减活动,eg:打八折,满300送100
先用上个学习到的简单工厂实现一下
建立一个现金收费的抽象类
package celv;
abstract class CashSuper {
// 现金收取超类的抽象方法,收取现金,参数为原价,返回伪当前价
public abstract double acceptCash(double money);
}
正常收费子类
package celv;
public class CashNormal extends CashSuper{
@Override
public double acceptCash(double money) {
// TODO Auto-generated method stub
return money;
}
}
折扣收费子类
package celv;
public class CashRebate extends CashSuper{
public double moneyRebate=1;
public CashRebate(String moneyRebate){
this.moneyRebate=Double.parseDouble(moneyRebate);
}
@Override
public double acceptCash(double money) {
// TODO Auto-generated method stub
return money*moneyRebate;
}
}
满减收费子类
package celv;
public class CashReturn extends CashSuper{
public double moneyCondition=0.0;
public double moneyReturn=0.0;
public CashReturn(String moneyCondition,String moneyReturn){
this.moneyCondition=Double.parseDouble(moneyCondition);
this.moneyReturn=Double.parseDouble(moneyReturn);
}
@Override
public double acceptCash(double money) {
// TODO Auto-generated method stub
double result=money;
if(money>=moneyCondition){
result=money-Math.floor(money/moneyCondition)*moneyReturn;
}
return result;
}
}
一个工厂类来生成不同实例
package celv;
public class CashFactory {
public static CashSuper createCashAccept(String type){
CashSuper cs=null;
switch(type){
case "收费正常":
CashNormal cs0=new CashNormal();
cs=cs0;
break;
case "满300返100":
CashReturn cr1=new CashReturn("300","100");
cs=cr1;
break;
case "打八折":
CashRebate cr2=new CashRebate("0.8");
cs=cr2;
break;
}
return cs;
}
}
测试
package celv;
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
double price=50;
double number=20;
double total=0;
String type="满300返100";
// 简单工厂模式
CashSuper csuper=CashFactory.createCashAccept(type);
total=csuper.acceptCash(price*number);
System.out.println(total);
}
}
上述可以完成基本功能要求,但是,工厂本身包括了所有的收费方式,商场是可能经常性地更改打折额度和返利额度,每次维护或扩展收费方式都要改动这个工厂,以至于代码需要重新编译部署。
策略模式:商场收银时如何促销,打折还是返利,其实都是一些算法,用工厂来生成算法对象没有错,但算法本身只是一种策略,最重要的是这些算法是随时都可能互相替换的,这就是变化点,封装变化点是面向对象的一种很重要的思维方式。
于是,可以不用工厂,建立一个Context上下文,用一个ConcreteStrategy来配置,维护一个对Strategy对象的应用,即:
package celv;
public class CashContext {
public CashSuper cs;
// 通过构造方法,传入具体的收费策略
public CashContext(CashSuper cs){
this.cs=cs;
}
// 根据收费策略的不同,获得计算结果
public double getResult(double money){
return cs.acceptCash(money);
}
}
此时,客户端的测试可以写为:
package celv;
public class celvTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
double price=50;
double number=20;
double total=0;
String type="满300返100";
CashContext cc=null;
switch(type){
case "收费正常":
cc=new CashContext(new CashNormal());
break;
case "满300返100":
cc=new CashContext(new CashReturn("300","100"));
break;
case "打八折":
cc=new CashContext(new CashRebate("0.8"));
break;
}
total=cc.getResult(price*number);
System.out.println(total);
}
}
但是也存在缺点,即让客户端去判断用哪一个算法,可以试着将简单工工厂和策略模式进行结合,将CashContext类修改
package celv;
public class CashContext {
public CashSuper cs;
// 通过构造方法,传入具体的收费策略
public CashContext(CashSuper cs){
this.cs=cs;
}
public CashContext(String type){
switch(type){
case "收费正常":
CashNormal cs0=new CashNormal();
cs=cs0;
break;
case "满300返100":
CashReturn cr1=new CashReturn("300","100");
cs=cr1;
break;
case "打八折":
CashRebate cr2=new CashRebate("0.8");
cs=cr2;
break;
}
}
// 根据收费策略的不同,获得计算结果
public double getResult(double money){
return cs.acceptCash(money);
}
}
那么,客户端代码就是这样子
package celv;
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
double price=50;
double number=20;
double total=0;
String type="满300返100";
// 策略模式与简单工厂结合
CashContext csuper=new CashContext(type);
total=csuper.getResult(price*number);
System.out.println(total);
}
}
由此可以看出,简单工厂模式让客户端认识两个类,即CashSuper和CashFactory,而策略模式与简单工厂结合,客户端只需要认识一个类CashContext,耦合度更低。
总结:策略模式是一种定义一系列算法的方法,从概念上看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试。
参考自《大话设计模式》,代码自己有所修改,持续更新。