zoukankan      html  css  js  c++  java
  • 策略模式原来这么简单!

    前言

    只有光头才能变强

    回顾前面:

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

    一、策略模式介绍

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

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

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

    当时解决方案是这样的:

    • 先定义一个接口:ResultSetHandler(调用者想要对结果集进行什么操作,只要实现这个接口即可)
      • 这个接口定义了行为。Object hanlder(ResultSet resultSet);
    • 然后实现上面的接口,比如我们要封装成一个Bean对象,就是public class BeanHandler implements ResultSetHandler
    • 调用的时候,实际上就是query()查询方法多一个参数 query(String sql, Object[] objects, ResultSetHandler rsh)。调用者想要返回什么类型,只要传入相对应的ResultSetHandler实现类就是了。

    代码如下:

    
    	query方法:
    
        //这个方法的返回值是任意类型的,所以定义为Object。
        public static Object query(String sql, Object[] objects, ResultSetHandler rsh) {
    
            Connection connection = null;
            PreparedStatement preparedStatement = null;
            ResultSet resultSet = null;
    
            try {
                connection = getConnection();
                preparedStatement = connection.prepareStatement(sql);
    
                //根据传递进来的参数,设置SQL占位符的值
                if (objects != null) {
                    for (int i = 0; i < objects.length; i++) {
                        preparedStatement.setObject(i + 1, objects[i]);
                    }
                }
    
    
                resultSet = preparedStatement.executeQuery();
    
                //调用调用者传递进来实现类的方法,对结果集进行操作
                return rsh.hanlder(resultSet);
        }
    
    	接口:
    
    	/*
        * 定义对结果集操作的接口,调用者想要对结果集进行什么操作,只要实现这个接口即可
        * */
        public interface ResultSetHandler {
             Object hanlder(ResultSet resultSet);
        
        }
    
    	接口实现类(Example):
    
    	//接口实现类,对结果集封装成一个Bean对象
    	public class BeanHandler implements ResultSetHandler {
    	
    	
    	    //要封装成一个Bean对象,首先要知道Bean是什么,这个也是调用者传递进来的。
    	    private Class clazz;
    	
    	    public BeanHandler(Class clazz) {
    	        this.clazz = clazz;
    	    }
    	
    	    @Override
    	    public Object hanlder(ResultSet resultSet) {
    	
    	        try {
    	
    	            //创建传进对象的实例化
    	            Object bean = clazz.newInstance();
    	
    	            if (resultSet.next()) {
    	
    	                //拿到结果集元数据
    	                ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
    	
    	                for (int i = 0; i < resultSetMetaData.getColumnCount(); i++) {
    	
    	                    //获取到每列的列名
    	                    String columnName = resultSetMetaData.getColumnName(i+1);
    	
    	                    //获取到每列的数据
    	                    String columnData = resultSet.getString(i+1);
    	
    	                    //设置Bean属性
    	                    Field field = clazz.getDeclaredField(columnName);
    	                    field.setAccessible(true);
    	                    field.set(bean,columnData);
    	                }
    	
    	                //返回Bean对象
    	                return bean;
    	            }
    
    

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

    1.1策略模式讲解

    《设计模式之禅》:

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

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

    策略模式通用类图

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

    • 策略的接口相当于我们上面所讲的ResultSetHandler接口(定义了策略的行为)
    • 具体的实现相当于我们上面所讲的BeanHandler实现(接口的具体实现)
      • 具体的实现一般还会有几个,比如可能还有ListBeanHandler、MapBeanHandler等等

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

    《设计模式之禅》:

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

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

    知乎问题

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

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

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

    具体的链接:

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

    DBUtils策略模式类图

    1.2策略模式例子

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

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

    接口:

    
    /**
     * 增加粉丝策略的接口(Strategy)
     */
    interface IncreaseFansStrategy {
    
        void action();
    }
    

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

    
    /**
     * 请水军(ConcreteStrategy)
     */
    public class WaterArmy implements IncreaseFansStrategy {
        
        @Override
        public void action() {
            System.out.println("3y牛逼,我要给你点赞、转发、加鸡腿!");
        }
    }
    

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

    
    /**
     * 认真写原创(ConcreteStrategy)
     */
    public class OriginalArticle implements IncreaseFansStrategy{
    
        @Override
        public void action() {
            System.out.println("3y认真写原创,最新一篇文章:《策略模式,就这?》");
    
        }
    }
    

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

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

    
    /**
     * 3y(Context)
     */
    public class Java3y {
    
        private IncreaseFansStrategy strategy ;
    
        public Java3y(IncreaseFansStrategy strategy) {
            this.strategy = strategy;
        }
    
        // 3y要发文章了(买水军了、送书了、写知乎引流了...)。
        // 具体执行哪个,看3y选哪个
        public void exec() {
            strategy.action();
        }
    }
    

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

    public class Main {
    
        public static void main(String[] args) {
    
            // 今天2018年12月24日
            Java3y java3y = new Java3y(new WaterArmy());
            java3y.exec();
    
            // 明天2018年12月25日
            Java3y java4y = new Java3y(new OriginalArticle());
            java4y.exec();
            
            // ......
        }
    }
    
    

    执行结果:

    执行结果

    1.3策略模式优缺点

    优点:

    • 算法可以自由切换
      • 改一下策略很方便
    • 扩展性良好
      • 增加一个策略,就多增加一个类就好了。

    缺点:

    • 策略类的数量增多
      • 每一个策略都是一个类,复用的可能性很小、类数量增多
    • 所有的策略类都需要对外暴露
      • 上层模块必须知道有哪些策略,然后才能决定使用哪一个策略

    调用方必须要知道有哪些策略

    1.4JDK的策略模式应用

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

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

    
        /**
         * Handler called when saturated or shutdown in execute.
         */
        private volatile RejectedExecutionHandler handler;
    
    	public ThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue,
                                  ThreadFactory threadFactory,
                                  RejectedExecutionHandler handler) {
            //....
            this.handler = handler;
        }
    
        /**
         * Invokes the rejected execution handler for the given command.
         * Package-protected for use by ScheduledThreadPoolExecutor.
         */
        final void reject(Runnable command) {
            handler.rejectedExecution(command, this);
        }
    
    

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

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

    最后

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

    推荐阅读和参考资料:

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

    帅的人都关注了

    文章的目录导航

  • 相关阅读:
    2020年9月29日
    随机生成验证码
    动手动脑java语法基础
    Java语法之动手实验
    代码大全2 读书笔记
    java动手动动脑之字串联接
    二柱子问题
    生成随机四则运算1
    可变参数
    2020年9月30日
  • 原文地址:https://www.cnblogs.com/Java3y/p/10190199.html
Copyright © 2011-2022 走看看