策略模式原来这么简单!
前言
只有光头才能变强
回顾前面:
无论是面试还是个人的提升,设计模式是必学的。今天来讲解策略模式~
一、策略模式介绍
我一次听到策略模式这个词,是在我初学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来调用更符合面向对象(屏蔽了直接对具体实现的访问)。
首先要明白一个道理,就是——到底是 “人” 旅游,还是火车、汽车、自行车、飞机这些交通工具旅游?
如果没有上下文的话,客户端就必须直接和具体的策略实现进行交互了,尤其是需要提供一些公共功能或者是存储一些状态的时候,会大大增加客户端使用的难度;引入上下文之后,这部分工作可以由上下文来完成,客户端只需要和上下文进行交互就可以了。这样可以让策略模式更具有整体性,客户端也更加的简单
具体的链接:
所以我们再说回上文的通用类图,我们就可以这样看了:
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。
文章的目录导航:
上一篇: lz脾气好(背景)……给闺蜜说