策略模式原来就这么简单!

前言

只有光头才能变强

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

一、策略模式介绍

我一次听到策略模式这个词,是在我初学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策略模式讲解

《设计模式之禅》:

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

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

bb

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

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

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

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

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

《设计模式之禅》:

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

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

bb

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

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

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

具体的链接:

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

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

bb

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. }

执行结果:

bb

1.3策略模式优缺点

优点:

  • 算法可以自由切换

    • 改一下策略很方便

  • 扩展性良好

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

缺点:

  • 策略类的数量增多

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

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

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

bb

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

  • 《设计模式之禅》

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/69900354/viewspace-2286250/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/69900354/viewspace-2286250/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值