策略模式---算法策略选择的“懒人用法”
策略模式并没有表面上那么高深,相反,它的实现意图非常浅显,就是我们程序员实在不想在判断一大堆条件后写一些基本上都差不多的代码(即算法的变体,明明是同一种算法,只是因为输入不一样就要重复一次),烦!没错,就是这种感觉,催生这种模式。我相信,当初设计这种模式的家伙一定是忍无可忍了。
public class ComparatorList implements Comparator { @Override public int compare(Object arg0, Object arg1) { Integer obj0 =(Integer) arg0; Integer obj1 = (Integer)arg1; int flag = obj0.compareTo(obj1); return - flag; } }
我们先实现一个Comparator,这里比较的是数字的大小,按照数列从低到高的顺序排序(默认顺寻是从高到低,所以这里flag要取反)。我们可以看到,compare的方法参数是两个Object对象,这样就能比较任何类型的对象,就算是自定义对象(基本类型必须用包装类型),然后我们就能在这个方法中自定义自己的比较方法。
public void sort(List<Integer> list) { ComparatorList comparator = new ComparatorList(); Collections.sort(list, comparator); }
这就是策略模式还有接口封装的使用。先说接口封装,我们定义一个接口,目的就是想要在所有实现类中定义一个通用规则,然后由用户自定义自己的方法,这样就能解决用户需求多样的问题。我们永远不知道用户的需要到底有多少,什么样的,所以,干脆将这个选择直接抛给用户而不是想当然的为他们设置限制(关于接口封装,以后会有专门介绍的)。继续回到我们的策略模式上来。如果我想要我的数列能够同时显示从高到低和从低到高的顺序,那么,我们可以继续实现一个Comparator---ComparatorList2,然后只要在我的代码中这样写:
ComparatorList2 comparator2 = new ComparatorList2();
Collections.sort(list, comparator2);
就能达到我们上面的目的,以此类推,如果想要比较其他类型的List,可以继续实现Comparator类。于是,我们就可以不必先判断List的元素类型然后调用对应的方法,否则真的会没完没了!
策略模式的原理是否很简单?如果还是有不清楚的,这里就提供一个简易的使用模型:
public interface Strategy { //策略方法 // public abstract double method(); } public class Strategy1 implements Strategy { @Override public void method() { System.out.println(this.getClass().getName() + "的方法");
} } public class Strategy2 implements Strategy { @Override
public void method() { System.out.println(this.getClass().getName() + "的方法"); } } public class Context { //上下文类 // private Strategy mstrategy; public Context(Strategy strategy) { this.mstrategy = strategy; } //策略方法 // public void method() { this.mstrategy.method(); } } 主类 public static void main(String[] args) { Context context = new Context(new Strategy1()); context.method();// 执行算法1 context = new Context(new Strategy2()); context.method();// 执行算法2 }
这是典型的策略模式的使用方法。我们将策略类作参数,传递给Context类,然后由它来启动它们。这样有什么好处呢?为什么不是直接调用策略类而是将策略类交给Context类来启动呢?因为间接层能为我们带来更多的操作。如果这里是想要更改一下算法,那么,我只需要写个这样的方法:
pulic void set(Strategy strategy){ mstrategy = strategy; }
就能将算法进行更改而不需要将没用的算法的代码去掉。这就是间接层的灵活性(因为使用间接层意味着逻辑分离),也是重构的手法中很多都是增加间接层的原因。当然,有个必须正视的问题,增加间接层除了带来使用上的灵活性之外,还带来效率上的问题,因为它具有额外的开销,一般情况下,这种考虑是不必要的,因为整个程序运行期间,主要影响效率的问题可能不是这些间接层带来的。
使用策略模式的好处就在于Context类,因为它就是策略选择与策略实现的间接层。对于这个间接层,我们可以看到,最基本的就是它一定含有接口类中定义的通用算法,因为这是一种委托关系,将这个计算交给传进来的具体算法。但是有一个问题,如果我们的策略类需要Context类的数据呢?按照一般人的思维来讲,会想到两种方法:
1.将Context类引用作为策略类的参数。这种做法的好处就是当我们的Context添加新的公共功能时,我们的策略类能够马上使用它们,但是坏处非常明显,就是Context类的信息隐藏被破坏了,我需要考虑放松Context类不想被公开的数据和方法的访问权限,像是提供包访问权限。
public int createInteger(CreateDate date){ //CreateDate接口 int num = date.create(); //CreateDate接口的create()方法 }
所以,使用策略模式还有一个好处,就是我的代码能够使用多个不同的通用接口,如果是模板方法,我们就难以进行这样的操作,这也是为什么相比抽象类的继承来说,我们更多使用接口的原因。
策略模式之所以有这些好处,是因为它实现了策略选择逻辑(就是开头讲的那些条件判断语句)与策略实现逻辑的完全分离。逻辑分离的好处就是我们可以在完全不知道具体实现的情况下毫无副作用的使用它们,以及我们修改实现代码时,选择逻辑可以完全不用知道依然安全的使用。所以,追求封装,是我们面向对象编程中永远的主题。