1. 策略模式
1.1 概念
策略模式(Strategy)定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化不会影响到使用算法的客户。
1.2 需求
现在需要实现一个商场收银软件,拥有打折和返利功能,并且营业员根据客户所购买商品的单价和数量,在客户端进行收费。
1.3 简单工厂模式实现
1.3.1 收费抽象类
public abstract class CashSuper{
//收取费用的抽象方法,参数为单价和数量
public abstract double acceptCash(double price,int num);
}
1.3.2 正常收费
public class CashNormal extends CashSuper {
//原价返回
public double acceptCash(double price,int num){
return price*num;
}
}
1.3.3 打折收费
public class CashRebate extends CashSuper {
private double moneyRebate = 1d;
//初始化时必须输入折扣率,八折就输入0.8
public CashRebate(double moneyRebate){
this.moneyRebate = moneyRebate;
}
//计算收费时需要在原价的基础上乘以折扣率
public double acceptCash(double price,int num){
return price*num*this.moneyRebate;
}
}
1.3.4 返利
public class CashReturn extends CashSuper(){
private double moneyCondition = 0d;//返利条件
private double moneyReturn = 0d;//返利值
//返利收费。初始化时需要输入返利条件和返利值
public CashReturn(double moneyCondition,double moneyReturn){
this.moneyCondition = moneyCondition;
this.moneyReturn = moneyReturn
}
//计算收费时,当达到返利条件,就原价减去返利值
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;
}
}
}
1.3.5 收费工厂
//收费工厂
public class CashFactory {
public static CashSuper createCashAccpet(int cashType){
CashSuper cs = null;
switch(cashType){
case 1:
cs = new CashNormal(); //正常收费
break;
case 2:
cs = new CashRebate(0.8d); //打八折
break;
case 3:
cs = new CashRebate(0.7d); //打七折
break;
case 4:
cs = new CashReturn(300d,100d); //满300返100
break;
}
return cs;
}
}
1.3.6 客户端程序主要部分
//简单工厂模式根据discount的数字选择合适的收费类生成实例
CashSuper csuper = CashFactory.createCashAccept(discount);
//通过多态,可以根据不同收费策略计算得到收费的结果
totalPrices = csuper.acceptCash(price,num);
total = total + totalPrices;
1.3.7 UML类图
1.3.8 弊端
简单工厂模式虽然也能解决这个问题,但这个模式只是解决对象的创建问题,而且由于工厂本身包括所有的收费方式,商场是可能经常性地更改打折额度和返利额度,每次维护或扩展收费方式都要改动这个工厂,以致代码需重新编译部署,这真的是很糟糕的处理方式,所以用它不是最好的办法。面对算法的时常变动,应该有更好的办法。因此下面介绍另外一种设计模式——策略模式
1.4 策略模式案例
商场收银时如何促销,用打折还是返利,其实都是一些算法,用工厂来生成算法对象,这没有错,但算法本身只是一种策略,最重要的是这些算法是随时都可能互相替换的,这就是变化点,而封装变化点是我们面向对象的一种很重要的思维方式。接下来介绍策略模式的结构图和基本代码。
1.4.1 UML类图
1.4.2 基本代码
1.4.2.1 抽象算法类
//抽象算法类
abstract class Strategy{
//算法方法
public abstract void algorithmInterface();
}
1.4.2.2 具体算法类
具体算法类,封装了具体的算法或行为,继承于Strategy
//具体算法类A
class ConcreteStrategyA extends Strategy{
//算法A实现方法
public void algorithmInterface(){
System.out.println("算法A实现");
}
}
//具体算法类B
class ConcreteStrategyB extends Strategy{
//算法B实现方法
public void algorithmInterface(){
System.out.println("算法B实现");
}
}
//具体算法类C
class ConcreteStrategyB extends Strategy{
//算法C实现方法
public void algorithmInterface(){
System.out.println("算法C实现");
}
}
1.4.2.3 上下文Context类
Context类,用一个ConcreteStrategy来配置,维护一个对Strategy对象的引用。
class Context{
Strategy strategy;
//初始化时,传入具体的策略对象
public Context(Strategy strategy){
this.strategy = strategy;
}
//上下文接口
public void contextInterface(){
//根据具体的策略对象,调用其算法的方法
strategy.algorithmInterface();
}
}
1.4.2.4 客户端代码
Context context;
//由于实例化不同策略,所以最终调用gcontext.contextInterface时,
//所获得的结果不同
context = new Context(new ConcreteStrategyA());
context.contextInterface();
context = new Context(new ConcreteStrategyB());
context.contextInterface();
context = new Context(new ConcreteStrategyC());
context.contextInterface();
1.5 策略模式实现
根据策略模式的思想,CashSuper类为抽象策略,而正常收费CashNormal、打折收费CashRebate和返利收费CashReturn就是三个具体策略,也就是策略模式中说的具体算法。
1.5.1 CashContext类
public class CashContext{
private CashSuper cs;
//通过构造方法,传入具体的收费策略
public CashContext(CashSuper csuper){
this.cs = csuper;
}
public double getResult(double price,int num){
//根据收费策略的不同,获得计算结果
return this.cs.acceptCash(price,num);
}
}
1.5.2 客户端主要代码
CashContext cc = null;
//根据用户输入,将对应的策略对象作为参数传入CashContext对象中
switch(discount){
case 1:
cc = new CashContext(new CashNormal());
break;
case 2:
cc = new CashContext(new CashRebate(0.8d));
break;
case 3:
cc = new CashContext(new CashRebate(0.7d));
break;
case 4:
cc = new CashContext(new CashReturn(300d,100d));
break;
}
//通过Context的getResult方法的调用可以获得收取费用的结果
//让具体算法和客户进行了隔离
totalPrices = cc.getResult(price,num);
total = total + totalPrices;
1.5.3 弊端
通过上述代码,这样又回到了原来的样子:在客户端判断用哪一个方法。因此,我们需要将判断的过程从客户端程序转移走,这时候就需要将策略模式的Context和简单工厂相结合
1.6 策略和简单工厂结合
1.6.1 改造后的CashContext
public class CashContext{
private CashSuper cs;
//通过构造方法,传入具体的收费策略
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.accpetCash(price,num)
}
}
1.6.2 客户端代码
//根据用户输入,将对应的策略对象作为参数传入CashContext对象中
CashContext cc = new CashContext(discount);
//通过Context的getResult方法的调用,获取收取费用的结果,从而让具体算法和客户进行隔离
totalPrices = cc.getResult(price,num);
total = total + totalPrices;
1.6.3 对比
//简单工厂模式的用法
CashSuper csuper = CashFactory.createCashAccept(discount);
totalPrices = csuper.acceptCash(price,num)
//策略模式与简单工厂结合的用法
CashContext cc new CashContext(discount);
totalPrices = cc.getResult(price,number)
简单工厂模式我需要让客户端认识两个类,CashSuper 和CashFactory,而策略模式与简单工厂结合的用法,客户端就只需要认识一个类CashContext就可以了,耦合更加降低。
客户端中实例化的是CashContext的对象,调用的是CashContext的方法GetResult,这使得具体的收费算法彻底地与客户端分离。连算法的父类CashSuper都不让客户端认识了,从而降低耦合度。
2. 总结
策略模式是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合。
那么它的优点如下:
- 策略模式的Strategy类层次为Context定义了一系列的可供重用的算法或行为。继承有助于析取出这些算法中的公共功能
- 简化了单元测试,因为每个算法都有自己的类,可以通过自己的接口单独测试
- 当不同的行为堆砌在一个类中时,就很难避免使用条件语句来选择合适的行为。将这些行为封装在一个个独立的Strategy类中,可以在使用这些行为的类中消除条件语句,因此,策略模式封装了变化。