本文探讨初学使用策略模式时遇到的一些疑惑,以及在工作中慢慢解决之前遇到的疑惑,借此与大家分享。比如说本文谈到策略模式中环境角色Context的用处,为什么一定要用,可不可以将此取消。这些都是在学习和工作的实践总结中慢慢体会到的。
首先,我们来看下策略模式的概念。一般的解释如下:
策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。(原文:The Strategy Pattern defines a family of algorithms,encapsulates each one,and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.)
一般的,策略模式主要分为以下三个角色:
1. 环境角色(Context):持有一个策略类引用
2. 抽象策略(Strategy):定义了多个具体策略的公共接口,具体策略类中各种不同的算法以不同的方式实现这个接口;Context使用这些接口调用不同实现的算法。一般的,我们使用接口或抽象类实现。
3. 具体策略(ConcreteStrategy):实现抽象策略类中的相关的算法或操作。
我们首先来写一个简单的策略模式,然后再结合实际应用进行扩展,进而思考其在实际开发中的使用方法。
(给大家推荐前辈写的一篇不错的博文,研磨设计模式之 策略模式 http://www.uml.org.cn/sjms/201009092.asp )
我们这个简单的策略假设就是想让不同的策略来实现某个算法(algorithm),抽象类如下:
算法algorithm的具体实现策略类ConcreteStrategy1和ConcreteStrategy2如下:
环境角色的实现如下:
下面简单写一个客户端测试的代码:
输出结果如下:
----------------我是策略一算法----------------
----------------我是策略二算法----------------
好吧,到此为止,一个简单的策略模式就写完了。但是,大家肯定有所疑惑,Context完全可以没有嘛,既然抽象策略AbstractStrategy已经持有algorithm这个接口,我们完全可以如下去写代码,让系统根据不同的实现执行不同的策略不就完了。代码可以如下:
输出结果如下:
----------------我是策略一算法----------------
----------------我是策略二算法----------------
可见,2种方案都实现了同一个引用根据不同的实现执行特定的算法。是的,分析发现,在上面简单的应用中,Context的确可以取消。那么,Context这个角色为什么还要存在呢?
让我们考虑以下几种情况:
1、如果我们需要对不同策略中相同算法的参数,执行相同的安全性检查,我们如果没有环境角色Context,则只能在每个实现的开始部分,调用安全性检查代码;而有了Context这个角色,我们可以在调用Context的构造器时,统一进行安全性检查。这在我们的实现策略比较多的时候,比如说7、8个的时候,特别有用,可以大量减少冗余的代码量。
2、如果我们需要改变原有算法时,需要引进新的参数,如果没有Context,我们怎么办?一种办法是重载该算法,增加新的函数接口;另外一种办法是完全废弃原有的函数接口,重新写新的函数接口。毋庸置疑,这2种办法的代价都很大,尤其是如果这个新的参数只有部分实现策略中的该算法实现用到的时候。而我们使用Context就可以完全解决这个问题。
下面我们改造下上面那个基本策略模式,我们让策略模式也持有对Context的引用,这样的优点是可以在策略类里回调的Context里的所有可用的变量或函数等信息。此外,我们也增加一个新的实现策略类ConcreteStrategy3,具体代码如下所示:
好了,现在我们想这样改变需求,ConcreteStrategy2和ConcreteStrategy3里对algorithm算法的实现,需要统计两个新的信息,分别用parameter1和parameter2来表示,同时要统计所有实现策略类里,对algorithm算法调用的次数。
如果没有Context这个角色,又需要做到客户端调用的时候代码改动尽量少,相信大家的做法只好是改抽象策略类和实现策略类里的algorithm算法。但是这样实现策略ConcreteStrategy1可能不愿意了,因为他并不需要新增加的参数;此外,对所有实现类里algorithm算法调用的统计也没有一个统一的入口,需要在每个algorithm实现中,插入一个计数代码。但是如果有了环境角色Context,一切就变得很简单了,我们不需要改动抽象策略类,和实现策略类ConcreteStrategy1,只需要改需求发生变化相关的类,且看下面的代码:
客户端测试的代码如下:
测试输出结果如下:
------------这是第1次调用algorithm算法--------
----------------我是策略一算法----------------
------------这是第2次调用algorithm算法--------
----------------我是策略二算法----------------
------------------我需要的参数parameter1=100
------------------我需要的参数parameter2=200
------------这是第3次调用algorithm算法--------
----------------我是策略三算法----------------
------------------我需要的参数parameter1=100
------------------我需要的参数parameter2=200
由以上分析可见,策略模式中,各个角色的功能都非常重要,虽然环境角色Context可以在某些简单的策略模式中不去使用,但是如果无法预测到各个实现策略功能和需求的变化,以及实现灵活性更好的策略模式,在使用策略模式进行架构时,一定要充分利用所有角色的功能。
首先,我们来看下策略模式的概念。一般的解释如下:
策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换。策略模式让算法独立于使用它的客户而独立变化。(原文:The Strategy Pattern defines a family of algorithms,encapsulates each one,and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.)
一般的,策略模式主要分为以下三个角色:
1. 环境角色(Context):持有一个策略类引用
2. 抽象策略(Strategy):定义了多个具体策略的公共接口,具体策略类中各种不同的算法以不同的方式实现这个接口;Context使用这些接口调用不同实现的算法。一般的,我们使用接口或抽象类实现。
3. 具体策略(ConcreteStrategy):实现抽象策略类中的相关的算法或操作。
我们首先来写一个简单的策略模式,然后再结合实际应用进行扩展,进而思考其在实际开发中的使用方法。
(给大家推荐前辈写的一篇不错的博文,研磨设计模式之 策略模式 http://www.uml.org.cn/sjms/201009092.asp )
我们这个简单的策略假设就是想让不同的策略来实现某个算法(algorithm),抽象类如下:
- package com.icecode.demo.strategy;
- /**
- * 抽象策略
- * @author zhanche
- *
- */
- public abstract class AbstractStrategy {
- /**
- * 某个希望有不同策略实现的算法
- */
- public abstract void algorithm();
- }
算法algorithm的具体实现策略类ConcreteStrategy1和ConcreteStrategy2如下:
- package com.icecode.demo.strategy.impl;
- import com.icecode.demo.strategy.AbstractStrategy;
- /**
- * 对算法的第一种具体实现策略
- * @author zhanche
- *
- */
- public class ConcreteStrategy1 extends AbstractStrategy {
- @Override
- public void algorithm() {
- System.out.println("----------------我是策略一算法----------------");
- }
- }
- package com.icecode.demo.strategy.impl;
- import com.icecode.demo.strategy.AbstractStrategy;
- /**
- * 对算法的第二种具体实现策略
- * @author zhanche
- *
- */
- public class ConcreteStrategy2 extends AbstractStrategy {
- @Override
- public void algorithm() {
- System.out.println("----------------我是策略二算法----------------");
- }
- }
环境角色的实现如下:
- package com.icecode.demo.context;
- import com.icecode.demo.strategy.AbstractStrategy;
- /**
- * 环境角色,主要完成对特定策略的调用
- * @author zhanche
- *
- */
- public class Context {
- private AbstractStrategy strategy;
- public Context(AbstractStrategy strategy) {
- this.strategy = strategy;
- }
- public void algorithm() {
- this.strategy.algorithm();
- }
- }
下面简单写一个客户端测试的代码:
- package com.icecode.demo;
- import com.icecode.demo.context.Context;
- import com.icecode.demo.strategy.impl.ConcreteStrategy1;
- import com.icecode.demo.strategy.impl.ConcreteStrategy2;
- /**
- * 策略模式测试类
- * @author zhanche
- *
- */
- public class Client {
- /**
- * @param args
- */
- public static void main(String[] args) {
- Context context = new Context(new ConcreteStrategy1());
- context.algorithm();
- context = new Context(new ConcreteStrategy2());
- context.algorithm();
- }
- }
输出结果如下:
----------------我是策略一算法----------------
----------------我是策略二算法----------------
好吧,到此为止,一个简单的策略模式就写完了。但是,大家肯定有所疑惑,Context完全可以没有嘛,既然抽象策略AbstractStrategy已经持有algorithm这个接口,我们完全可以如下去写代码,让系统根据不同的实现执行不同的策略不就完了。代码可以如下:
- package com.icecode.demo;
- import com.icecode.demo.strategy.AbstractStrategy;
- import com.icecode.demo.strategy.impl.ConcreteStrategy1;
- import com.icecode.demo.strategy.impl.ConcreteStrategy2;
- /**
- * 策略模式测试类
- * @author zhanche
- *
- */
- public class Client {
- /**
- * @param args
- */
- public static void main(String[] args) {
- AbstractStrategy strategy = new ConcreteStrategy1();
- strategy.algorithm();
- strategy = new ConcreteStrategy2();
- strategy.algorithm();
- }
- }
输出结果如下:
----------------我是策略一算法----------------
----------------我是策略二算法----------------
可见,2种方案都实现了同一个引用根据不同的实现执行特定的算法。是的,分析发现,在上面简单的应用中,Context的确可以取消。那么,Context这个角色为什么还要存在呢?
让我们考虑以下几种情况:
1、如果我们需要对不同策略中相同算法的参数,执行相同的安全性检查,我们如果没有环境角色Context,则只能在每个实现的开始部分,调用安全性检查代码;而有了Context这个角色,我们可以在调用Context的构造器时,统一进行安全性检查。这在我们的实现策略比较多的时候,比如说7、8个的时候,特别有用,可以大量减少冗余的代码量。
2、如果我们需要改变原有算法时,需要引进新的参数,如果没有Context,我们怎么办?一种办法是重载该算法,增加新的函数接口;另外一种办法是完全废弃原有的函数接口,重新写新的函数接口。毋庸置疑,这2种办法的代价都很大,尤其是如果这个新的参数只有部分实现策略中的该算法实现用到的时候。而我们使用Context就可以完全解决这个问题。
下面我们改造下上面那个基本策略模式,我们让策略模式也持有对Context的引用,这样的优点是可以在策略类里回调的Context里的所有可用的变量或函数等信息。此外,我们也增加一个新的实现策略类ConcreteStrategy3,具体代码如下所示:
- package com.icecode.demo.strategy;
- import com.icecode.demo.context.Context;
- /**
- * 抽象策略
- * @author zhanche
- *
- */
- public abstract class AbstractStrategy {
- /**
- * 某个希望有不同策略实现的算法
- */
- public abstract void algorithm(Context context);
- }
- package com.icecode.demo.strategy.impl;
- import com.icecode.demo.context.Context;
- import com.icecode.demo.strategy.AbstractStrategy;
- /**
- * 对算法的第一种具体实现策略
- * @author zhanche
- *
- */
- public class ConcreteStrategy1 extends AbstractStrategy {
- @Override
- public void algorithm(Context context) {
- System.out.println("----------------我是策略一算法----------------");
- }
- }
- package com.icecode.demo.strategy.impl;
- import com.icecode.demo.context.Context;
- import com.icecode.demo.strategy.AbstractStrategy;
- /**
- * 对算法的第二种具体实现策略
- * @author zhanche
- *
- */
- public class ConcreteStrategy2 extends AbstractStrategy {
- @Override
- public void algorithm(Context context) {
- System.out.println("----------------我是策略二算法----------------");
- }
- }
- package com.icecode.demo.strategy.impl;
- import com.icecode.demo.context.Context;
- import com.icecode.demo.strategy.AbstractStrategy;
- /**
- * 对算法的第三种具体实现策略
- * @author zhanche
- *
- */
- public class ConcreteStrategy3 extends AbstractStrategy {
- @Override
- public void algorithm(Context context) {
- System.out.println("----------------我是策略三算法----------------");
- }
- }
- package com.icecode.demo.context;
- import com.icecode.demo.strategy.AbstractStrategy;
- /**
- * 环境角色,主要完成对特定策略的调用
- * @author zhanche
- *
- */
- public class Context {
- /**
- * 持有对策略的引用
- */
- private AbstractStrategy strategy;
- /**
- * 算法入口
- */
- public void algorithm() {
- this.strategy.algorithm(this);
- }
- }
好了,现在我们想这样改变需求,ConcreteStrategy2和ConcreteStrategy3里对algorithm算法的实现,需要统计两个新的信息,分别用parameter1和parameter2来表示,同时要统计所有实现策略类里,对algorithm算法调用的次数。
如果没有Context这个角色,又需要做到客户端调用的时候代码改动尽量少,相信大家的做法只好是改抽象策略类和实现策略类里的algorithm算法。但是这样实现策略ConcreteStrategy1可能不愿意了,因为他并不需要新增加的参数;此外,对所有实现类里algorithm算法调用的统计也没有一个统一的入口,需要在每个algorithm实现中,插入一个计数代码。但是如果有了环境角色Context,一切就变得很简单了,我们不需要改动抽象策略类,和实现策略类ConcreteStrategy1,只需要改需求发生变化相关的类,且看下面的代码:
- package com.icecode.demo.context;
- import com.icecode.demo.strategy.AbstractStrategy;
- /**
- * 环境角色,主要完成对特定策略的调用
- * @author zhanche
- *
- */
- public class Context {
- /**
- * 持有对策略的引用
- */
- private AbstractStrategy strategy;
- /**
- * parameter1、parameter2只是ConcreteStrategy2ConcreteStrategy3需要使用的参数,
- * 而ConcreteStrategy1不使用
- */
- private int parameter1;
- private int parameter2;
- //count用来统计所有策略的算法algorithm调用的总次数
- public static int count = 0;
- public Context(AbstractStrategy strategy) {
- this.strategy = strategy;
- }
- public Context(AbstractStrategy strategy, int parameter1, int parameter2) {
- super();
- this.strategy = strategy;
- this.parameter1 = parameter1;
- this.parameter2 = parameter2;
- }
- public int getParameter1() {
- return parameter1;
- }
- public int getParameter2() {
- return parameter2;
- }
- /**
- * 算法入口
- */
- public void algorithm() {
- count++;
- System.out.println("------------这是第"+count+"次调用algorithm算法--------");
- this.strategy.algorithm(this);
- }
- }
- package com.icecode.demo.strategy.impl;
- import com.icecode.demo.context.Context;
- import com.icecode.demo.strategy.AbstractStrategy;
- /**
- * 对算法的第一种具体实现策略
- * @author zhanche
- *
- */
- public class ConcreteStrategy1 extends AbstractStrategy {
- @Override
- public void algorithm(Context context) {
- System.out.println("----------------我是策略一算法----------------");
- }
- }
- package com.icecode.demo.strategy.impl;
- import com.icecode.demo.context.Context;
- import com.icecode.demo.strategy.AbstractStrategy;
- /**
- * 对算法的第二种具体实现策略
- * @author zhanche
- *
- */
- public class ConcreteStrategy2 extends AbstractStrategy {
- @Override
- public void algorithm(Context context) {
- System.out.println("----------------我是策略二算法----------------");
- System.out.println("------------------我需要的参数parameter1="+context.getParameter1());
- System.out.println("------------------我需要的参数parameter2="+context.getParameter2());
- }
- }
- package com.icecode.demo.strategy.impl;
- import com.icecode.demo.context.Context;
- import com.icecode.demo.strategy.AbstractStrategy;
- /**
- * 对算法的第三种具体实现策略
- * @author zhanche
- *
- */
- public class ConcreteStrategy3 extends AbstractStrategy {
- @Override
- public void algorithm(Context context) {
- System.out.println("----------------我是策略二算法----------------");
- System.out.println("------------------我需要的参数parameter1="+context.getParameter1());
- System.out.println("------------------我需要的参数parameter2="+context.getParameter2());
- }
- }
客户端测试的代码如下:
- package com.icecode.demo;
- import com.icecode.demo.context.Context;
- import com.icecode.demo.strategy.impl.ConcreteStrategy1;
- import com.icecode.demo.strategy.impl.ConcreteStrategy2;
- import com.icecode.demo.strategy.impl.ConcreteStrategy3;
- /**
- * 策略模式测试类
- * @author zhanche
- *
- */
- public class Client {
- /**
- * @param args
- */
- public static void main(String[] args) {
- Context context = new Context(new ConcreteStrategy1());
- context.algorithm();
- context = new Context(new ConcreteStrategy2(),100, 200);
- context.algorithm();
- context = new Context(new ConcreteStrategy3(), 100, 200);
- context.algorithm();
- }
- }
测试输出结果如下:
------------这是第1次调用algorithm算法--------
----------------我是策略一算法----------------
------------这是第2次调用algorithm算法--------
----------------我是策略二算法----------------
------------------我需要的参数parameter1=100
------------------我需要的参数parameter2=200
------------这是第3次调用algorithm算法--------
----------------我是策略三算法----------------
------------------我需要的参数parameter1=100
------------------我需要的参数parameter2=200
由以上分析可见,策略模式中,各个角色的功能都非常重要,虽然环境角色Context可以在某些简单的策略模式中不去使用,但是如果无法预测到各个实现策略功能和需求的变化,以及实现灵活性更好的策略模式,在使用策略模式进行架构时,一定要充分利用所有角色的功能。