Java设计模式之策略模式
1. 策略模式的介绍
通常我们在软件开发中,经常会遇到一种情况,那就是,我们在实现某一个功能可以有多种算法或者策略,我们会根据实际的情况选择不同的算法或者策略来完成该功能,列如,我们在我们的简单的计算器中,我们可以使用加法、减法、乘法、除法等。针对这种情况,我们可能最常见的方式就是将加、减、乘、除写在一个类中。然后分别用四个方法表示 ,当我们需要做加法运算的时候,我们常用的做法就是使用if…else或者switch…case等条件语句来判断语句来选择我们需要具体操作的算法,这两种实现方式我们一般可以叫他为硬编码。然后一旦有很多算法都集中在这个类中,那么很明显代码会变得非常臃肿,导致这个类的维护成本非常高,在维护时还容易发生一些错误。并且,一旦我们要扩展功能,比如需要添加%求余操作,那么我们需要修改封装好了的类的源代码。这样明显的违反了我们的OCP原则和单一原则。所以为了解决这个问题,我们可以很好使用策略模式来实现。策略模式的原理就是,我们将这些算法或者策略抽象出来,提供一个统一的接口,不同的算法或者策略有不同的实现类,这样在程序客户端就可以通过注入不同的实现对象来实现算法或者策略的动态替换,这种模式非常具有可扩展性、可维护性。
2. 策略模式的定义
策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们可以相互替换。策略模式让算法独立于使用它的客户而独立变化。
3. 策略模式的使用场景
3.1 针对同一类型的问题有多种不同的处理方式,仅仅是具体的行为有差异时。
3.2 需要安全地封装多种同一类型的操作时。
3.3 出现同一抽象类有多个子类,而又需要使用if-else或者switch-case来选择具体子类时。
4. 策略模式的UML类图
Context——用来操作策略的类
Stragety——抽象的策略
ConcreteStageA——具体的策略实现
5. 策略模式的简单实现
现在我们要实现一个计算器的一些基本功能,实现加、减、乘、除四个功能,在使用策略模式之前,我们先看一个我们最长用的写法,通过使用if-else来获取我们具体要实现的算法功能。代码如下:
Calculate .java
public class Calculate {
private static final String ADD = "+";
private static final String SUB = "-";
private static final String MUL = "*";
private static final String DIV = "/";
/**
* 加法
* @param a
* @param b
* @return
*/
public static int add(int a,int b){
return a + b;
}
/**
* 减法
* @param a
* @param b
* @return
*/
public static int sub(int a, int b){
return a - b;
}
/**
* 乘法
* @param a
* @param b
* @return
*/
public static int mul(int a, int b){
return a * b;
}
/**
* 除法
* @param a
* @param b
* @return
*/
public static double div(int a, int b){
return 1.0 * a / b;
}
public static void main(String[] args) {
int a = 10;
int b = 20;
System.out.println("请输入要运算的操作+,-,*,/");
String operator = new Scanner(System.in).nextLine();
if(ADD.equals(operator)){
System.out.println("a + b = " + add(a, b));
}else if(SUB.equals(operator)){
System.out.println("a - b = " + sub(a, b));
}else if(MUL.equals(operator)){
System.out.println("a * b = " + mul(a, b));
}else if(DIV.equals(operator)){
System.out.println("a / b = " + div(a, b));
}
}
}
对于上面的代码,很明显他不符合我们的OCP原则和单一原则,假如上面的类,我们是已经封装好了,现在我们又要添加一个功能,添加一个%求余的算法功能,那么我们就又要在修改源码的基础上去添加一个求余方法,然后手动的在if-else里面添加相应的判断,如果一不小心,也很容易写错代码,那么这样对我们整个程序的可扩展性和可维护性打大折扣了。所以,为了解决这样的问题,我们可以使用下面的策略模式来实现相应的功能,代码如下:
CalulateStragety.java
/**
* 定义一组可以扩展功能的策略接口
* 以后有不同的功能可以往里面添加
* @param <T>
*/
public interface CalulateStragety<T> {
/**
* 具体的计算实现由其子类实现
* @param a
* @param b
* @return
*/
T calculate(T a, T b);
}
Add .java
/**
* 具体需要实现的算法
*/
public class Add implements CalulateStragety<Integer> {
/**
* 加法的实现具体
* @param a
* @param b
* @return
*/
@Override
public Integer calculate(Integer a, Integer b) {
return a + b;
}
}
Sub .java
public class Sub implements CalulateStragety<Integer>{
/**
* 减法的具体实现
* @param a
* @param b
* @return
*/
@Override
public Integer calculate(Integer a, Integer b) {
return a - b;
}
}
Mul .java
public class Mul implements CalulateStragety<Integer>{
/**
* 乘法的具体实现
* @param a
* @param b
* @return
*/
@Override
public Integer calculate(Integer a, Integer b) {
return a * b;
}
}
Div .java
public class Div implements CalulateStragety<Double> {
/**
* 除法的具体实现
* @param a
* @param b
* @return
*/
@Override
public Double calculate(Double a, Double b) {
return a / b;
}
}
CalcContext .java
/**
* 用来操作具体的策略
*/
public class CalcContext {
private CalulateStragety calulateStragety;
/**
* 注册需要具体实现的算法
* @param calulateStragety
*/
public void setStragety(CalulateStragety calulateStragety){
this.calulateStragety = calulateStragety;
}
/**
* 代理调用计算的方法
* @param a
* @param b
* @param <T>
* @return
*/
public <T> T calulate(T a, T b){
return (T)calulateStragety.calculate(a,b);
}
public static void main(String[] args) {
int a = 10;
int b = 20;
CalcContext calcContext = new CalcContext();
calcContext.setStragety(new Add());
System.out.println("a + b = " + calcContext.calulate(a,b));
calcContext.setStragety(new Sub());
System.out.println("a - b = " + calcContext.calulate(a,b));
calcContext.setStragety(new Mul());
System.out.println("a * b = " + calcContext.calulate(a,b));
calcContext.setStragety(new Div());
System.out.println("a / b = " + calcContext.calulate(1.0 * a,1.0 * b));
}
}
通过上面的代码,我们可以很明显的看出二者之间的区别,前者通过if-else来解决问题,虽然实现上较为简单,类型层级单一,但是暴露的问题非常明显,即代码臃肿,逻辑复杂,不便于维护和扩展,没有结构性,后者则是通过建立抽象,将不同的策略构建成一个具体的策略实现,通过不同的策略实现算法替换,比如我们需要添加一个%求余的功能,只需要自定义一个求余类实现策略接口,然后通过操作类通过setStragty方法往里面注册一下即可。并且我们在简化逻辑、结构的同时,增加了系统的可读性、稳定性、可扩展性,这对于较为复杂的业务逻辑显得更为直观。