策略模式原来这么简单!

49次阅读

共计 4831 个字符,预计需要花费 13 分钟才能阅读完成。

前言
只有光头才能变强
回顾前面:

给女朋友讲解什么是代理模式
包装模式就是这么简单啦
单例模式你会几种写法?
工厂模式理解了没有?

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

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 来包装一下,就完事了。
推荐阅读和参考资料:

https://www.cnblogs.com/lewis0077/p/5133812.html
《设计模式之禅》

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

文章的目录导航:
https://github.com/ZhongFuCheng3y/3y

正文完
 0