策略模式
策略模式的用意是针对一组算法,将每一个算法封装到具有共同接口的独立类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端(原有类)的情况下发生变化。
策略模式的结构
策略模式是对算法的包装,是把使用算法的责任和算法本身分开。策略模式通常是把一系列的算法包装到一系列的策略类里面,作为一个抽象策略类的子类。
策略模式涉及到三个角色:
1、环境角色
持有一个策略Strategy的引用
2、抽象策略角色
这是一个抽象角色,通常由一个接口或抽象类实现,此角色给出所有具体策略类所需的接口
3、具体策略角色
包装了相关算法或行为
策略模式在java中的使用
在java中的一个非常经典的策略模式就是java中的Comparator接口,这就是属于抽象的策略角色,我们实现的该接口的实现类,也就是自定义的排序规则就是属于具体的策略角色。
我们在使用Arrays.sort()方法对我们自定义类型的对象进行排序的时候,往往会要求我们传入一个比较器,这个比较器就是具体的策略角色,也就是一个具体的算法,我们不用对原有的java中的Arrays.sort()方法进行任何的改变,直接传入不同的比较器就能使用不同的比较策略。
我们来想一下,如果没有策略模式,我们应该怎么来写这个代码?
那么在Arrays.sort()类中,我们就需要去频繁的改变其代码,每新增一种需要排序的类型,我们都要去其代码中新增一个if语句,然后添加具体的排序策略,这很明显就违背了一个非常基础的原则,也就是开闭原则。
策略模式的缺点是什么
缺点就在于我们在使用策略模式的对象的使用,需要知道每一种具体的策略。
就比如上面的Arrays.sort(),我们使用该方法对数组排序的时候,我们需要知道我们排序的数组的类型,以此来决定传入的具体的算法,如果没有使用策略模式,我们不需要知道任何的算法策略,直接传入一个数组,Arrays.sort()自动根据我们的类型进行排序。
使用了策略模式之后将策略的选择进行了迁移,本来需要在在Arrays.sort()方法中进行的判断我们转换到了我们客户端代码上来,使原有的Arrays.sort()类不需要进行任何的改变,但是又增加了我们客户端的使用成本,客户端需要知道所有的策略,每增加一种新的策略,我们还是需要去修改我们的代码。
工厂模式,单例模式,策略模式结合
其实可以把策略模式和单例,工厂模式和单例结合起来,完全的实现开闭原则,下面就用代码进行展示,还是以上面的Arrays.sort()为例子,我们自定义一些的Compator策略,使用一个工厂来管理这些策略。
两个比较器,也就是两个策略
public class IntCompareStrategy implements Comparator<Integer> {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
}
public class DoubleCompareStrategy implements Comparator<Double> {
@Override
public int compare(Double o1, Double o2) {
return (int) (o1 - o2);
}
}
工厂类
public class ComparatorStrategyFactory {
/**
* 工厂类统一管理所有的策略类
*/
private HashMap<Class, Comparator> strategyMap = new HashMap<>();
private ComparatorStrategyFactory() {
if (Lazy.INSTANCE != null) {
throw new RuntimeException("单例禁止创建多个实例");
}
init();
}
public static ComparatorStrategyFactory getInstance() {
return Lazy.INSTANCE;
}
public <T> Comparator getStrategy(Class<T> clazz) {
Comparator comparator = strategyMap.get(clazz);
if (comparator == null) {
if (Comparable.class.isAssignableFrom(clazz)) {
comparator = strategyMap.get(Comparable.class);
} else {
comparator = strategyMap.get(Object.class);
}
}
return comparator;
}
/**
* 初始化所有的比较器
*/
private void init() {
/**
* 这里可以修改成从配置文件中读取,或者使用去读取注解标识的类,以达到真正的开闭原则
*/
strategyMap.put(Integer.class, new IntCompareStrategy());
strategyMap.put(Double.class, new DoubleCompareStrategy());
strategyMap.put(Comparable.class, new DefaultComparableStrategy());
strategyMap.put(Object.class, new DefaultStrategy());
}
private static class Lazy {
private static final ComparatorStrategyFactory INSTANCE = new ComparatorStrategyFactory();
}
// 默认的比较器
private class DefaultStrategy implements Comparator<Object> {
@Override
public int compare(Object o1, Object o2) {
return o1.hashCode() - o2.hashCode();
}
}
// 默认的继承了Comparable接口的比较器
private class DefaultComparableStrategy implements Comparator<Comparable> {
@Override
public int compare(Comparable o1, Comparable o2) {
return o1.compareTo(o2);
}
}
}
public class StrategyTest {
public static void main(String[] args) {
Integer[] nums = new Integer[32];
for (int i = 0; i < 32; i++) {
nums[i] = (int) (Math.random() * 100);
}
System.out.println(Arrays.toString(nums));
// 客户端在调用的时候不需要知道具体有什么策略类,把所有的策略集中在工厂进行管理,客户端只需要向工厂要策略就可以了
Arrays.sort(nums, ComparatorStrategyFactory.getInstance().getStrategy(Integer.class));
System.out.println(Arrays.toString(nums));
}
}
核心代码就在工厂类里面,工厂统一管理所有的策略,客户端不需要知道任何的策略,这样在增加新的策略的时候我们也不需要去修改客户端的代码,因为客户端的代码一般比较核心,修改容易出现问题。工厂管理了所有的策略之后,可以通过多种方式获得策略,可以通过注解或者配置文件的方式来标识策略类,以达到真正的开闭原则。
上面的这种思想就来源于spirngMVC,springMVC中对Controller的处理就是上面的方式,每一个Controller里面的方法都相当于一个策略,DispatcherServlet这个类就相当于客户端,他需要使用很多的策略来应对不同的请求,这就是策略模式的一个非常好的应用。