前言

只有光头才能变强

回顾前面:

  • 给女朋友讲解什么是代理模式

  • 包装模式就是这么简单啦

  • 单例模式你会几种写法?

  • 工厂模式理解了没有?

无论是面试还是个人的提升,设计模式是必学的。今天来讲解策略模式~

一、策略模式介绍

我一次听到策略模式这个词,是在我初学JDBC的时候。不知道大家有没有用过DBUtils这个组件。当时初学跟着视频学习,方立勋老师首先是让我们先自己封装一下JDBC的一些常用的操作(实际上就是模仿DBUtils这个组件)。

当时候的问题是这样的:我们打算封装一下 query()查询方法,传入的参数有 Stringsql,Object[]objects(指定SQL语句和对应的参数)。我们想根据不同的业务返回不同的值。

  • 比如说,有的时候我们返回的是一条数据,那我们想将这条数据封装成一个Bean对象

  • 比如说,有的时候我们返回的是多条数据,那我们想将这多条数据封装成一个 List<Bean> 集合

  • 比如说,有的时候我们返回的是xxxx数据,那我们想将这多条数据封装成一个 Map<Bean> 集合

  • ........等等等

当时解决方案是这样的:

  • 先定义一个接口:ResultSetHandler(调用者想要对结果集进行什么操作,只要实现这个接口即可)

    • 这个接口定义了行为。 Objecthanlder(ResultSetresultSet);

  • 然后实现上面的接口,比如我们要封装成一个Bean对象,就是 publicclassBeanHandlerimplementsResultSetHandler

  • 调用的时候,实际上就是 query()查询方法多一个参数 query(Stringsql,Object[]objects,ResultSetHandlerrsh)。调用者想要返回什么类型,只要传入相对应的ResultSetHandler实现类就是了。

代码如下:

  1.    query方法:


  2.    //这个方法的返回值是任意类型的,所以定义为Object。

  3.    public static Object query(String sql, Object[] objects, ResultSetHandler rsh) {


  4.        Connection connection = null;

  5.        PreparedStatement preparedStatement = null;

  6.        ResultSet resultSet = null;


  7.        try {

  8.            connection = getConnection();

  9.            preparedStatement = connection.prepareStatement(sql);


  10.            //根据传递进来的参数,设置SQL占位符的值

  11.            if (objects != null) {

  12.                for (int i = 0; i < objects.length; i++) {

  13.                    preparedStatement.setObject(i + 1, objects[i]);

  14.                }

  15.            }



  16.            resultSet = preparedStatement.executeQuery();


  17.            //调用调用者传递进来实现类的方法,对结果集进行操作

  18.            return rsh.hanlder(resultSet);

  19.    }


  20.    接口:


  21.    /*

  22.    * 定义对结果集操作的接口,调用者想要对结果集进行什么操作,只要实现这个接口即可

  23.    * */

  24.    public interface ResultSetHandler {

  25.         Object hanlder(ResultSet resultSet);


  26.    }


  27.    接口实现类(Example):


  28.    //接口实现类,对结果集封装成一个Bean对象

  29.    public class BeanHandler implements ResultSetHandler {



  30.        //要封装成一个Bean对象,首先要知道Bean是什么,这个也是调用者传递进来的。

  31.        private Class clazz;


  32.        public BeanHandler(Class clazz) {

  33.            this.clazz = clazz;

  34.        }


  35.        @Override

  36.        public Object hanlder(ResultSet resultSet) {


  37.            try {


  38.                //创建传进对象的实例化

  39.                Object bean = clazz.newInstance();


  40.                if (resultSet.next()) {


  41.                    //拿到结果集元数据

  42.                    ResultSetMetaData resultSetMetaData = resultSet.getMetaData();


  43.                    for (int i = 0; i < resultSetMetaData.getColumnCount(); i++) {


  44.                        //获取到每列的列名

  45.                        String columnName = resultSetMetaData.getColumnName(i+1);


  46.                        //获取到每列的数据

  47.                        String columnData = resultSet.getString(i+1);


  48.                        //设置Bean属性

  49.                        Field field = clazz.getDeclaredField(columnName);

  50.                        field.setAccessible(true);

  51.                        field.set(bean,columnData);

  52.                    }


  53.                    //返回Bean对象

  54.                    return bean;

  55.                }

这就是策略模式??就这??这不是多态的使用吗??

1.1策略模式讲解

《设计模式之禅》:

定义一组算法,将每个算法都封装起来,并且使他们之间可以互换

策略模式的类图是这样的:

image.png

策略的接口和具体的实现应该很好理解:

  • 策略的接口相当于我们上面所讲的ResultSetHandler接口(定义了策略的行为)

  • 具体的实现相当于我们上面所讲的BeanHandler实现(接口的具体实现)

    • 具体的实现一般还会有几个,比如可能还有ListBeanHandler、MapBeanHandler等等

令人想不明白的可能是:策略模式还有一个Context上下文对象。这对象是用来干什么的呢?

《设计模式之禅》:

Context叫做上下文角色,起承上启下封装作用,屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化。

在知乎上也有类似的问题(为什么不直接调用,而要通过Person?):

image.png

说白了,通过Person来调用更符合面向对象(屏蔽了直接对具体实现的访问)。

首先要明白一个道理,就是——到底是 “人” 旅游,还是火车、汽车、自行车、飞机这些交通工具旅游?

如果没有上下文的话,客户端就必须直接和具体的策略实现进行交互了,尤其是需要提供一些公共功能或者是存储一些状态的时候,会大大增加客户端使用的难度;引入上下文之后,这部分工作可以由上下文来完成,客户端只需要和上下文进行交互就可以了。这样可以让策略模式更具有整体性,客户端也更加的简单

具体的链接:

  • https://www.zhihu.com/question/31162942

所以我们再说回上文的通用类图,我们就可以这样看了:

image.png

1.2策略模式例子

现在3y拥有一个公众号,名称叫做Java3y。3y想要这让更多的人认识到Java3y这个公众号。所以每天都在想怎么涨粉(hahah

于是3y就开始想办法了(操碎了心),同时3y在这一段时间下来发现涨粉的方式有很多。为了方便,定义一个通用的接口方便来管理和使用呗。

接口:

  1. /**

  2. * 增加粉丝策略的接口(Strategy)

  3. */

  4. interface IncreaseFansStrategy {


  5.    void action();

  6. }

涨粉的具体措施,比如说,请水军:

  1. /**

  2. * 请水军(ConcreteStrategy)

  3. */

  4. public class WaterArmy implements IncreaseFansStrategy {


  5.    @Override

  6.    public void action() {

  7.        System.out.println("3y牛逼,我要给你点赞、转发、加鸡腿!");

  8.    }

  9. }

涨粉的具体措施,比如说,认真写原创:

  1. /**

  2. * 认真写原创(ConcreteStrategy)

  3. */

  4. public class OriginalArticle implements IncreaseFansStrategy{


  5.    @Override

  6.    public void action() {

  7.        System.out.println("3y认真写原创,最新一篇文章:《策略模式,就这?》");


  8.    }

  9. }

3y还想到了很多涨粉的方法,比如说送书活动啊、商业互吹啊等等等...(这里就不细说了)

说到底,无论是哪种涨粉方法,都是通过3y去执行的。

  1. /**

  2. * 3y(Context)

  3. */

  4. public class Java3y {


  5.    private IncreaseFansStrategy strategy ;


  6.    public Java3y(IncreaseFansStrategy strategy) {

  7.        this.strategy = strategy;

  8.    }


  9.    // 3y要发文章了(买水军了、送书了、写知乎引流了...)。

  10.    // 具体执行哪个,看3y选哪个

  11.    public void exec() {

  12.        strategy.action();

  13.    }

  14. }

所以啊,每当到了发推文的时候,3y就可以挑用哪种方式涨粉了:

  1. public class Main {


  2.    public static void main(String[] args) {


  3.        // 今天2018年12月24日

  4.        Java3y java3y = new Java3y(new WaterArmy());

  5.        java3y.exec();


  6.        // 明天2018年12月25日

  7.        Java3y java4y = new Java3y(new OriginalArticle());

  8.        java4y.exec();


  9.        // ......

  10.    }

  11. }

执行结果:

image.png

1.3策略模式优缺点

优点:

  • 算法可以自由切换

    • 改一下策略很方便

  • 扩展性良好

    • 增加一个策略,就多增加一个类就好了。

缺点:

  • 策略类的数量增多

    • 每一个策略都是一个类,复用的可能性很小、类数量增多

  • 所有的策略类都需要对外暴露

    • 上层模块必须知道有哪些策略,然后才能决定使用哪一个策略

image.png

1.4JDK的策略模式应用

不知道大家还能不能想起ThreadPoolExecutor(线程池):线程池你真不来了解一下吗?

学习ThreadPoolExecutor(线程池)就肯定要知道它的构造方法每个参数的意义:

  1.    /**

  2.     * Handler called when saturated or shutdown in execute.

  3.     */

  4.    private volatile RejectedExecutionHandler handler;


  5.    public ThreadPoolExecutor(int corePoolSize,

  6.                              int maximumPoolSize,

  7.                              long keepAliveTime,

  8.                              TimeUnit unit,

  9.                              BlockingQueue<Runnable> workQueue,

  10.                              ThreadFactory threadFactory,

  11.                              RejectedExecutionHandler handler) {

  12.        //....

  13.        this.handler = handler;

  14.    }


  15.    /**

  16.     * Invokes the rejected execution handler for the given command.

  17.     * Package-protected for use by ScheduledThreadPoolExecutor.

  18.     */

  19.    final void reject(Runnable command) {

  20.        handler.rejectedExecution(command, this);

  21.    }

其中我们可以找到RejectedExecutionHandler,这个参数代表的是拒绝策略(有四种具体的实现:直接抛出异常、使用调用者的线程来处理、直接丢掉这个任务、丢掉最老的任务)

其实这就是策略模式的体现了。

最后

看完会不会觉得策略模式特别简单呀?就一个算法接口、多个算法实现、一个Context来包装一下,就完事了。

推荐阅读和参考资料:

  • https://www.cnblogs.com/lewis0077/p/5133812.html

  • 《设计模式之禅》

乐于分享和输出干货的Java技术公众号:Java3y。

image.png

文章的目录导航

  • https://github.com/ZhongFuCheng3y/3y



更多相关文章

  1. 3.docker网络模式,桥接模式
  2. 图解 *** 等 6 种前端渲染模式
  3. 一场函数式思维模式的洗礼
  4. 阻塞队列实现生产者消费者模式
  5. 从JDK中,我们能学到哪些设计模式?
  6. 构造模式实践
  7. 工厂模式理解了没有?
  8. 单例模式的十种写法,你会几个?
  9. 单例模式你会几种写法?

随机推荐

  1. 机器学习爱好者必读的入门指南
  2. 不变的就是变化本身(Vue学习笔记one)
  3. 用Python分析5187位CSDN博主数据,顺便把昨
  4. 用Excel、SQL、Python做数据分析有何不同
  5. 数据分析和数据科学的四个时代
  6. 笨办法学Python,其实一点都不笨
  7. 手把手教你用 Python 进行机器学习
  8. 如何正确的获取数据?
  9. maven技术+IDEA配置maven
  10. 思科链路聚合(捆绑)