策略模式原来这么简单! 逃离我推掉我的手 2021-09-17 10:52 365阅读 0赞 # 前言 # > 只有光头才能变强 回顾前面: * [给女朋友讲解什么是代理模式][Link 1] * [包装模式就是这么简单啦][Link 2] * [单例模式你会几种写法?][Link 3] * [工厂模式理解了没有?][Link 4] 无论是面试还是个人的提升,设计模式是必学的。今天来讲解**策略**模式~ # 一、策略模式介绍 # 我一次听到策略模式这个词,是在我初学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策略模式讲解 ## 《设计模式之禅》: > 定义一组算法,将每个算法都封装起来,并且使他们之间可以互换 策略模式的类图是这样的: ![策略模式通用类图][20181228140202385] 策略的接口和具体的实现应该很好理解: * 策略的接口相当于我们上面所讲的ResultSetHandler接口(定义了策略的行为) * 具体的实现相当于我们上面所讲的BeanHandler实现(接口的具体实现) * 具体的实现一般还会有几个,比如可能还有ListBeanHandler、MapBeanHandler等等 令人想不明白的可能是:策略模式还有一个**Context上下文对象**。这对象是用来干什么的呢? 《设计模式之禅》: > Context叫做上下文角色,起承上启下封装作用,**屏蔽高层模块对策略、算法的直接访问**,封装可能存在的变化。 在知乎上也有类似的问题(为什么不直接调用,而要通过Person?): ![知乎问题][20181228140202430] 说白了,通过Person来调用**更符合面向对象**(屏蔽了直接对具体实现的访问)。 > 首先要明白一个道理,就是——到底是 “人” 旅游,还是火车、汽车、自行车、飞机这些交通工具旅游? > 如果没有上下文的话,客户端就必须直接和具体的策略实现进行交互了,尤其是需要提供一些公共功能或者是存储一些状态的时候,会大大增加客户端使用的难度;引入上下文之后,这部分工作可以由上下文来完成,客户端只需要和上下文进行交互就可以了。这样可以让策略模式更具有整体性,客户端也更加的简单 具体的链接: * [https://www.zhihu.com/question/31162942][https_www.zhihu.com_question_31162942] 所以我们再说回上文的通用类图,我们就可以这样看了: ![DBUtils策略模式类图][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(); // ...... } } 执行结果: ![执行结果][20181228140202521] ## 1.3策略模式优缺点 ## 优点: * 算法可以自由切换 * 改一下策略很方便 * 扩展性良好 * 增加一个策略,就多增加一个类就好了。 缺点: * 策略类的数量增多 * 每一个策略都是一个类,复用的可能性很小、类数量增多 * 所有的策略类都需要对外暴露 * **上层模块必须知道有哪些策略**,然后才能决定使用哪一个策略 ![调用方必须要知道有哪些策略][20181228140202565] ## 1.4JDK的策略模式应用 ## 不知道大家还能不能想起ThreadPoolExecutor(线程池):[线程池你真不来了解一下吗?][Link 5] 学习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来包装一下,就完事了。 推荐阅读和参考资料: * [https://www.cnblogs.com/lewis0077/p/5133812.html][https_www.cnblogs.com_lewis0077_p_5133812.html] * 《设计模式之禅》 > 乐于分享和输出**干货**的Java技术公众号:Java3y。 ![帅的人都关注了][20181228140202609] 文章的**目录导航**: * [https://github.com/ZhongFuCheng3y/3y][https_github.com_ZhongFuCheng3y_3y] [Link 1]: https://mp.weixin.qq.com/s?__biz=MzI4Njg5MDA5NA==&mid=2247484222&idx=1&sn=5191aca33f7b331adaef11c5e07df468&chksm=ebd7423fdca0cb29cdc59b4c79afcda9a44b9206806d2212a1b807c9f5879674934c37c250a1#rd [Link 2]: https://mp.weixin.qq.com/s?__biz=MzI4Njg5MDA5NA==&mid=2247484226&idx=1&sn=a354d872978fc5db79a012c27ab5277f&chksm=ebd74243dca0cb5515fcad1ecfeda6a329be38c30a82a1325a5a19e7f62b0fa3c74069eed24d#rd [Link 3]: https://mp.weixin.qq.com/s?__biz=MzI4Njg5MDA5NA==&mid=2247484239&idx=1&sn=6560be96e456b513cb1e4f78a740a258&chksm=ebd7424edca0cb584906fb97679cf2ca557f430fbc87d2c86ce0652d2e3c36c2528466942df5#rd [Link 4]: https://mp.weixin.qq.com/s?__biz=MzI4Njg5MDA5NA==&mid=2247484243&idx=1&sn=972cbe6cdb578256e4d4771e7ca25de3&chksm=ebd74252dca0cb44419903758e8ca52d9ab287562f80be9365e305d6dcc2deaa45b40f9fd2e9&token=1258000567&lang=zh_CN#rd [20181228140202385]: /images/20210827/1a63fcc0bc0b4be694fd4d79a18ff911.png [20181228140202430]: /images/20210827/d7fa78c2e3734e5cac062a2ff0064974.png [https_www.zhihu.com_question_31162942]: https://www.zhihu.com/question/31162942 [DBUtils]: /images/20210827/2676e69c4d1145aa8f1714aeb0f6e5a2.png [20181228140202521]: /images/20210827/3b48c928dde34ad089f4e87b042df37c.png [20181228140202565]: /images/20210827/94fd3ff6b7934269bb8bf4859a103be9.png [Link 5]: https://mp.weixin.qq.com/s?__biz=MzI4Njg5MDA5NA==&mid=2247484214&idx=1&sn=9b5c977e0f8329b2bf4c29d230c678fb&chksm=ebd74237dca0cb212f4505935f9905858b9166beddd4603c3d3b5386b5dd8cf240c460a8e7c4#rd [https_www.cnblogs.com_lewis0077_p_5133812.html]: https://www.cnblogs.com/lewis0077/p/5133812.html [20181228140202609]: /images/20210827/05a1013993c54def88f33903b879beb9.png [https_github.com_ZhongFuCheng3y_3y]: https://github.com/ZhongFuCheng3y/3y
还没有评论,来说两句吧...