[把你的理性思维慢慢变成条件反射]
本文,我们讲介绍策略模式,文章主题结构与上位一致。惯例,先来看看我们示例工程的环境:
操作系统:win7 x64
其他软件:eclipse mars,jdk7
-------------------------------------------------------------------------------------------------------------------------------------经典问题:
销售结算系统中的费用结算,在实际情况下,经常存在对于不同类型,级别的客户,或不同的商品,采用不同的折扣计价策略。
思路分析:
要点一:客户,存在不同类型。要点二:商品,存在不同类型的折扣策略。
综合以上内容,系统中可能出现不同类型的调用方或者不同客户的参数。同时,运算方法存在多分支。
示例工程:
错误写法(1):
创建CalculatorDebate.java,具体内容如下:
package com.csdn.ingo.gof_strategy.base;
import java.util.Scanner;
public class CalculatorDebate {
public enum Debate {
// 通过括号赋值,而且必须带有一个参构造器和一个属性跟方法,否则编译出错
// 赋值必须都赋值或都不赋值,不能一部分赋值一部分不赋值;如果不赋值则不能写构造器,赋值编译也出错
_9("0.9"), _8("0.8"), _7("0.7"), _6("0.6"), _5("0.5");
private final String value;
// 构造器默认也只能是private, 从而保证构造函数只能在内部使用
Debate(String value) {
this.value = value;
}
public double getValue() {
return Double.parseDouble(value);
}
}
public static void main(String[] args) {
double total = 0d;
Scanner scan = new Scanner(System.in);
while (true) {
System.out.println("please enter number:");
int numA = scan.nextInt();
System.out.println("please enter prize:");
int numB = scan.nextInt();
System.out.println("please enter debate:");
for(Debate debate : Debate.values()){
System.out.print(debate+",");
}
String d = scan.next();
Debate debate = Debate.valueOf("_"+d);
total = (total + numA * numB);
System.out.println("当前总价:" + total);
System.out.println("折扣:" + debate.getValue());
System.out.println("折后:" + total*debate.getValue());
}
}
}
错误原因:
虽然代码功能是正确的,并且能够满足不同的折扣策略。但是,请注意,这里的计算方法严格限制了只能结算一次,只能采用固定折扣计算方法,即9折,8折等类似的计算方法。而对于满减,根据客户级别的结算策略完全无法使用。并且,违反了我们第一篇文章介绍的“开闭原则”等诸多原则。故,这是一种极其不好的解决方案。错误写法(2):
创建CachFactory.java文件,具体内容如下:
package com.csdn.ingo.gof_strategy.one;
public class CachFactory {
public static CashSuper createCashFactory(String type){
CashSuper cs = null;
switch(type){
case "1":
cs = new CashNormal();
break;
case "mj":
cs = new CashReturn("300", "100");
break;
case "2":
cs = new CashRebate("0.8");
break;
}
return cs;
}
}
创建CashNormal.java文件,具体内容如下:
package com.csdn.ingo.gof_strategy.one;
public class CashNormal extends CashSuper{
@Override
public double acceptCash(double money) {
return money;
}
}
创建CashRebate.java文件,具体内容如下:
package com.csdn.ingo.gof_strategy.one;
public class CashRebate extends CashSuper {
public double moneyRebate = 1d;
public CashRebate(String moneyRebate){
this.moneyRebate = Double.parseDouble(moneyRebate);
}
@Override
public double acceptCash(double money) {
// TODO Auto-generated method stub
return money*moneyRebate;
}
}
创建CashReturn.java文件,具体内容如下:
package com.csdn.ingo.gof_strategy.one;
public class CashReturn extends CashSuper{
private double moneyCondition = 0d;
private double moneyReturn =0d;
public CashReturn(String moneyCondition,String moneyReturn){
this.moneyCondition = Double.parseDouble(moneyCondition);
this.moneyReturn = Double.parseDouble(moneyReturn);
}
@Override
public double acceptCash(double money) {
double result = money;
if(money>=moneyCondition){
result = money-Math.floor(money/moneyCondition)*moneyReturn;
}
return result;
}
}
创建CashSuper.java文件,具体内容如下:
package com.csdn.ingo.gof_strategy.one;
public abstract class CashSuper {
public abstract double acceptCash(double money);
}
创建Window.java文件,具体内容如下:
package com.csdn.ingo.gof_strategy.one;
import java.util.Scanner;
public class Window {
public static void main(String[] args) {
double total = 0d;
String isEnd = "y";
Scanner scan = new Scanner(System.in);
while(isEnd.equals("y")){
System.out.println("please enter number:");
int numA = scan.nextInt();
System.out.println("please enter prize:");
int numB = scan.nextInt();
total = (total+numA*numB);
System.out.println("当前总价:"+total);
System.out.println("继续添加(y/n):");
isEnd = scan.next();
}
CashSuper csuper = CachFactory.createCashFactory("2");
total= csuper.acceptCash(total);
System.out.println("折扣:"+"2");
System.out.println("折后总价:"+total);
}
}
错误原因:
version_one的代码,虽然在功能上实现了无限制的商品计价,并且在代码设计上讲不通的策略进行隔离,满足了“单一职责原则”,“开闭原则”等。并且使用前文的简单工厂模式加以实现。但是,我们需要注意谨慎简单工厂模式的使用。因为,该模式只是负责创建了不同类型的对象而已。细心的读者应该记得,我们前文介绍过简单工厂模式的限制条件:通过静态方法创建实例对象。换句话说,对于经常改变营销策略的销售系统,每次都需要重新开发,编译软件的代价是巨大的。所以,该方案的错误原因是不符合实际需求。
错误写法(2):
创建CashContext.java文件,具体内容如下:
package com.csdn.ingo.gof_strategy.three;
public class CashContext {
private CashSuperStrategy cashSuperStrategy;
public CashContext(CashSuperStrategy cashSuperStrategy) {
this.cashSuperStrategy = cashSuperStrategy;
}
public double getResult(double money) {
return cashSuperStrategy.acceptCash(money);
}
}
创建CashNormal.java文件,具体内容如下:
package com.csdn.ingo.gof_strategy.three;
public class CashNormal extends CashSuperStrategy{
@Override
public double acceptCash(double money) {
return money;
}
}
创建CashRebate.java文件,具体内容如下:
package com.csdn.ingo.gof_strategy.three;
public class CashRebate extends CashSuperStrategy {
public double moneyRebate = 1d;
public CashRebate(String moneyRebate){
this.moneyRebate = Double.parseDouble(moneyRebate);
}
@Override
public double acceptCash(double money) {
// TODO Auto-generated method stub
return money*moneyRebate;
}
}
创建CashReturn.java文件,具体内容如下:
package com.csdn.ingo.gof_strategy.three;
public class CashReturn extends CashSuperStrategy{
private double moneyCondition = 0d;
private double moneyReturn =0d;
public CashReturn(String moneyCondition,String moneyReturn){
this.moneyCondition = Double.parseDouble(moneyCondition);
this.moneyReturn = Double.parseDouble(moneyReturn);
}
@Override
public double acceptCash(double money) {
double result = money;
if(money>=moneyCondition){
result = money-Math.floor(money/moneyCondition)*moneyReturn;
}
return result;
}
}
创建CashSuperStrategy.java文件,具体内容如下:
package com.csdn.ingo.gof_strategy.three;
public abstract class CashSuperStrategy {
public abstract double acceptCash(double money);
}
创建Window.java文件,具体内容如下:
package com.csdn.ingo.gof_strategy.three;
import java.util.Scanner;
public class WIndow {
public static void main(String[] args) {
double total = 0d;
String isEnd = "y";
Scanner scan = new Scanner(System.in);
while(isEnd.equals("y")){
System.out.println("please enter number:");
int numA = scan.nextInt();
System.out.println("please enter prize:");
int numB = scan.nextInt();
total = (total+numA*numB);
System.out.println("当前总价:"+total);
System.out.println("继续添加(y/n):");
isEnd = scan.next();
}
CashContext cs = null;
String type = "fl";
switch(type){
case "1":
cs = new CashContext(new CashNormal());
break;
case "fl":
cs = new CashContext(new CashReturn("300", "100"));
break;
case "2":
cs = new CashContext(new CashRebate("0.8"));
break;
}
total= cs.getResult(total);
System.out.println("折扣:"+"1");
System.out.println("折后总价:"+total);
}
}
错误原因:
version_three的已经实现了我们将要介绍策略模式的主体设计。但是,在Window.java文件中,出现了一个违反“单一职责原则”的写法,及策略的判断留给了客户端。在实际开发中,客户端是丝毫不需要关注这部分操作的,于是,我们需要将这部分内容转移到其他地方。
推荐写法:
修改CashContext.java文件,具体内容如下:
package com.csdn.ingo.gof_strategy.four;
import com.csdn.ingo.gof_strategy.three.CashNormal;
import com.csdn.ingo.gof_strategy.three.CashRebate;
import com.csdn.ingo.gof_strategy.three.CashReturn;
import com.csdn.ingo.gof_strategy.three.CashSuperStrategy;
public class CashContext {
private CashSuperStrategy cashSuperStrategy;
public CashContext(String type) {
switch(type){
case "1":
cashSuperStrategy = new CashNormal();
break;
case "fl":
cashSuperStrategy = new CashReturn("300", "100");
break;
case "2":
cashSuperStrategy = new CashRebate("0.8");
break;
}
}
public double getResult(double money) {
return cashSuperStrategy.acceptCash(money);
}
}
修改Window.java文件,具体内容如下:
package com.csdn.ingo.gof_strategy.four;
import java.util.Scanner;
public class Window {
public static void main(String[] args) {
double total = 0d;
String isEnd = "y";
Scanner scan = new Scanner(System.in);
while(isEnd.equals("y")){
System.out.println("please enter number:");
int numA = scan.nextInt();
System.out.println("please enter prize:");
int numB = scan.nextInt();
total = (total+numA*numB);
System.out.println("当前总价:"+total);
System.out.println("继续添加(y/n):");
isEnd = scan.next();
}
CashContext cs = new CashContext("2");
total= cs.getResult(total);
System.out.println("折后总价:"+total);
}
}
推荐原因:
- 符合“单一职责原则”,“开闭原则”等
- 客户端,每个计算单元均是独立的,达到易扩展,易维护的目的。
- 使用方调用不同策略的方式完全相同,减少了调用方与提供方之间的耦合度。并且提高了算法的重用次数。
- 提供了单元测试能力,因为,每一个策略都有独立的实现过程。
- Context类将条件语句封装为类的行为,将各种类型规则封装为统一的外在表现。有效减少了其他组件的功能及负担。特别的:对于条件语句的更高级的实现方式将在后文介绍(配置文件中设置具体子类名,反射机制),这里我们先将其独立为类行为,方便后续操作。敬请期待!
模式总结:
UML结构图:
概念总结:
策略模式:该模式定义了一系列算法的集合,分别封装为独立的单元,使得他们之间可以相互替换,并且使得算法的变化,不会影响到使用该算法的客户。
组成部分:Context,抽象父类,具体子类,三部分组成。
用法总结:
根据结构图,创建3个基本组成部分。客户端只需要传入算法必须的参数,Context负责决定具体策略,每个具体的实现类均独立。特别注意:客户端的代码中不要加入条件判断等多余代码,防止后期维护时产生不可预知的错误。另外,作为模式的讲解,我们暂时不介绍如何通过配置文件的方式实现动态策略调整,更多内容请各位看官自行学习。
反思:
应用场景:
- 系统中存在需要根据某些条件动态的选择一个算法实现,而这些算法具有某个相同的共性。换句话说,能够抽象出一个父类。
- 系统中某个对象的行为不确定,无法满足“单一职责原则”时。
- 希望对客户端屏蔽算法的实现细节,以提高安全性。
优点:
- 完全符合“开闭原则”。能够实现在不修改原有系统的同时,灵活的增加新的算法及实现类。
- 将众多的具体实现抽象为一个父类,一定程度上的减少了重复的公共代码。
- Context类将策略的选择功能完全的独立出来,同事避免了多重选择语句,方便的实现了后续的调整。
- 极大的简化了客户端的代码量,方便了客户端的使用。
缺点:
- 当具体的策略类非常多时,客户端虽然不需要显示的判断,但仍需要非常清楚不同策略之间的区别。
- 造成系统内过多的策略算法文件。
- 无法组合策略。客户端只能选择调用某一个具体的策略执行。
特别注意:
上文中,我们给出的示例代码是通过多重选择的形式来执行具体的实现类。在实际使用时,客户端也可以直接通过指明实现类的方式来调用具体的实现类(里氏代换原则)。
-------------------------------------------------------------------------------------------------------------------------------------
至此,被说了很多遍的设计模式---策略模式 结束
参考资料:
图书:《大话设计模式》
其他博文:http://blog.csdn.NET/lovelion/article/details/7563445