策略模式、策略模式与Spring的碰撞
策略模式是GoF23种设计模式中比较简单的了,也是常用的设计模式之一,今天我们就来看看策略模式。
实际案例
我工作第三年的时候,重构旅游路线的机票查询模块,旅游路线分为四种情况:
- 如果A地-B地往返都可以直达,那么查询两张机票(往返)
- 如果A地-B地去程无法直达,需要中转,但是返程可以直达,那么查询三张机票(去程两张,返程一张)
- 如果A地-B地去程可以直达,但是返程需要中转,那么查询三张机票(去程一张,返程两张)
- 如果A地-B地往返都无法直达,那么查询四张机票(去程两张,返程两张)
在我重构前,代码差不多是这样的:
int type = 1;
// 往返都可以直达
if (type == 1) {
// 查询出两张机票
return;
}
// 去程无法直达,需要中转,但是返程可以直达
if (type == 2) {
// 查询出三张机票(去程两张,返程一张)
return;
}
// 去程可以直达,但是返程需要中转
if (type == 3) {
// 查询出三张机票(去程一张,返程两张)
return;
}
// 往返都无法直达
else{
// 查询出四张机票(去程两张,返程两张)
return;
}
当时我还是菜鸡(现在也是),也不懂什么设计模式,就是感觉代码都写在一个类中,实在是太长了,不够清爽,不管是哪种类型的线路,最终都是返回机票集合,只是处理逻辑不同,可以提取一个接口出来,再开四个类去实现此接口,最后定义一个Map,Key是Type,Value是接口(实现类),根据Type决定调用哪个实现类,就像下面的酱紫:
public class Ticket {
private String desc;
public Ticket(String desc) {
this.desc = desc;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return "Ticket{" +
"desc='" + desc + '\'' +
'}';
}
}
public interface QueryTicketService {
List<Ticket> getTicketList();
}
public class QueryTicketAService implements QueryTicketService {
@Override
public List<Ticket> getTicketList() {
List<Ticket> list = new ArrayList<>();
list.add(new Ticket("去程机票"));
list.add(new Ticket("返程机票"));
return list;
}
}
public class QueryTicketBService implements QueryTicketService {
@Override
public List<Ticket> getTicketList() {
List<Ticket> list = new ArrayList<>();
list.add(new Ticket("去程第一张机票"));
list.add(new Ticket("去程第二张机票"));
list.add(new Ticket("返程机票"));
return list;
}
}
public class QueryTicketCService implements QueryTicketService {
@Override
public List<Ticket> getTicketList() {
List<Ticket> list = new ArrayList<>();
list.add(new Ticket("去程机票"));
list.add(new Ticket("返程第一张机票"));
list.add(new Ticket("返程第二张机票"));
return list;
}
}
public class QueryTicketDService implements QueryTicketService {
@Override
public List<Ticket> getTicketList() {
List<Ticket> list = new ArrayList<>();
list.add(new Ticket("去程第一张机票"));
list.add(new Ticket("去程第二张机票"));
list.add(new Ticket("返程第一张机票"));
list.add(new Ticket("返程第二张机票"));
return list;
}
}
public class Main {
static Map<Integer, QueryTicketService> map = new HashMap<>();
static {
map.put(1, new QueryTicketAService());
map.put(2, new QueryTicketBService());
map.put(3, new QueryTicketCService());
map.put(4, new QueryTicketDService());
}
public static void main(String[] args) {
int type = 1;
System.out.println(map.get(type).getTicketList());
}
}
运行结果:
[Ticket{desc='去程机票'}, Ticket{desc='返程机票'}]
当初我也不知道什么设计模式,就是感觉这样写完,代码清爽多了,后来才知道这就是策略模式的雏形了。
GoF23种设计模式真正应用广泛的设计模式不多,但是策略模式绝对算其中之一了,你看,当初我都不懂这些,就写出了策略模式的雏形。
原始的策略模式
如果我们遇到类似于上面的需求,第一反应肯定是用if else语句或者switch语句,根据不同的情况执行不同的代码,这样做也没什么大问题,但是我们的项目会越来越复杂,这么做的缺陷就慢慢的显现了出来:如果现在线路新增了一个类型,需要中转两次,就又得加好几个判断的分支(去程中转一次,返程中转两次;去程中转两次,返程中转一次;去程直达,返程中转两次等等),想想就恐怖,这样分支会越来越多,代码会越来越长,越来越难以维护,所以策略模式出现了。
当一个逻辑中,有很多if else语句或者switch语句,而且它们需要解决的问题是一样的,就可以考虑策略模式。
最原始的策略模式有三个角色:
- Strategy:抽象策略角色,对算法、策略的抽象,定义每个算法、策略所必需的方法,通常为接口。
- ConcreteStrategy:具体策略角色,实现抽象策略角色,完成具体的算法、策略。
- Context:上下文环境角色,保存了ConcreteStrategy,负责调用ConcreteStrategy。
而我上面的代码,就有了策略模式的味道,有了Strategy,也有了ConcreteStrategy,缺少的就是Context,如果用最原始的设计模式的写法来实现,是酱紫的:
public class Context {
static Map<Integer, QueryTicketStrategy> map = new HashMap<>();
static {
map.put(1, new QueryTicketAConcreteStrategy());
map.put(2, new QueryTicketBConcreteStrategy());
map.put(3, new QueryTicketCConcreteStrategy());
map.put(4, new QueryTicketDConcreteStrategy());
}
public void getTicketList(int type) {
System.out.println(map.get(type).getTicketList());
}
}
public class Main {
public static void main(String[] args) {
Context context = new Context();
context.getTicketList(1);
}
}
运行结果:
[Ticket{desc='去程机票'}, Ticket{desc='返程机票'}]
在这里,我把类名重新定义了下,让人一眼就可以看出这里使用了策略模式,这也是阿里推荐的命名方法。
策略模式是不是很简单(我在学习设计模式的时候,甚至觉得它比单例、简单工厂还要简单),而且特别实用,下面我们来看看策略模式的UML图:

JDK中的策略模式
既然策略模式那么实用,那么在JDK中有策略模式的应用吗?当然有。JDK中定义的Comparator接口就是策略模式的一种实践了:
public class SortLengthComparator implements Comparator<String> {
@Override
public int compare(String o1, String o2) {
return (o1.length() - o2.length() > 0) ? 1 : -1;
}
}
public class Main {
public static void main(String[] args) {
List<String>list=new ArrayList<>();
list.add("hello");
list.add("world");
list.add("codebear");
list.add("balabala");
list.add("java");
list.sort(new SortLengthComparator());
System.out.println(list);
}
}
我定义了一个比较器,实现了Comparator接口,重写了compare方法,实现了以比较字符串长度来比较字符串的功能。
运行结果:
[java, world, hello, balabala, codebear]
Comparator接口就是Strategy,我定义的SortLengthComparator就是ConcreteStrategy。
Comparator结合Lambda,会产生怎样的火花
定义一个比较器,虽然不难,但是总觉得不够简洁,不够方便,需要新建一个类,所以现在越来越多的人使用Lambda来进行排序,就像下面的酱紫:
List<String>list=new ArrayList<>();
list.add("hello");
list.add("world");
list.add("codebear");
list.add("balabala");
list.add("java");
List<String> newList = list.stream().sorted((a, b) -> (a.length() - b.length() > 0) ? 1 : -1).collect(Collectors.toList());
newList.forEach(System.out::println);
虽然底层还是用的Comparator,但是这样的写法清爽多了,如果比较的策略比较复杂,或者有多个地方都需要用到这个比较策略,还是用最原始的写法更好一些。
策略模式与Spring的碰撞
现在我们已经知道了什么是策略模式,如何使用策略模式,但是还有一个天大的问题,要知道,现在每个项目都在用Spring,如果你还是这么写的话:
public class Context {
static Map<Integer, QueryTicketStrategy> map = new HashMap<>();
static {
map.put(1, new QueryTicketAConcreteStrategy());
map.put(2, new QueryTicketBConcreteStrategy());
map.put(3, new QueryTicketCConcreteStrategy());
map.put(4, new QueryTicketDConcreteStrategy());
}
public void getTicketList(int type) {
System.out.println(map.get(type).getTicketList());
}
}
就意味着实现类里面的依赖需要自己去维护,无法使用神奇的@Autowired注解,所以策略模式与Spring碰撞,策略模式必须发生一点改变,而这改变让策略模式变得更加简单,性能更好,也更加迷人。
写法1
@Service
public class QueryTicketAConcreteStrategy implements QueryTicketStrategy {
@Override
public List<Ticket> getTicketList() {
List<Ticket> list = new ArrayList<>();
list.add(new Ticket("去程机票"));
list.add(new Ticket("返程机票"));
return list;
}
}
@Service
public class QueryTicketDConcreteStrategy implements QueryTicketStrategy {
@Override
public List<Ticket> getTicketList() {
List<Ticket> list = new ArrayList<>();
list.add(new Ticket("去程第一张机票"));
list.add(new Ticket("去程第二张机票"));
list.add(new Ticket("返程第一张机票"));
list.add(new Ticket("返程第二张机票"));
return list;
}
}
@Service
public class Context {
@Autowired
private QueryTicketStrategy queryTicketAConcreteStrategy;
@Autowired
private QueryTicketStrategy queryTicketDConcreteStrategy;
private static Map<Integer, QueryTicketStrategy> map = new HashMap<>();
@PostConstruct
public void init() {
map.put(1, queryTicketAConcreteStrategy);
map.put(4, queryTicketAConcreteStrategy);
}
public void getTicketList(int type) {
System.out.println(map.get(type).getTicketList());
}
}
@SpringBootApplication
public class Main {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(Main.class, args);
run.getBean(Context.class).getTicketList(1);
}
}
运行结果:
[Ticket{desc='去程机票'}, Ticket{desc='返程机票'}]
原始的设计模式有一个缺点,不管是具体的策略实现类,还是上下文类,都不是单例模式,而我们的方法在大多数情况下是无状态的,所以改成单例模式是非常合适的,而结合了Spring,我们完全不需要手写单例模式,Spring就帮我们完成了。
写法2(自认为最优雅)
不管是原始的策略模式,还是Spring与策略模式结合的第一种写法,都没有完全符合开闭原则,如果有新的策略引入,必须修改上下文类,往map里面添加一组新的映射关系,而第二种写法完美的解决了这个问题,而且让策略模式变得非常优雅,下面直接放出代码:
@Service("1")
public class QueryTicketAConcreteStrategy implements QueryTicketStrategy {
@Override
public List<Ticket> getTicketList() {
List<Ticket> list = new ArrayList<>();
list.add(new Ticket("去程机票"));
list.add(new Ticket("返程机票"));
return list;
}
}
@Service("4")
public class QueryTicketDConcreteStrategy implements QueryTicketStrategy {
@Override
public List<Ticket> getTicketList() {
List<Ticket> list = new ArrayList<>();
list.add(new Ticket("去程第一张机票"));
list.add(new Ticket("去程第二张机票"));
list.add(new Ticket("返程第一张机票"));
list.add(new Ticket("返程第二张机票"));
return list;
}
}
@Service
public class Context {
@Autowired
private Map<String, QueryTicketStrategy> map = new HashMap<>();
public void getTicketList(int type) {
String typeStr = String.valueOf(type);
System.out.println(map.get(typeStr).getTicketList());
}
}
@SpringBootApplication
public class Main {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(Main.class, args);
run.getBean(Context.class).getTicketList(1);
}
}
运行结果:
[Ticket{desc='去程机票'}, Ticket{desc='返程机票'}]
这就是Spring和神奇、迷人之处了,竟然可以自动注入map,key就是beanName,value就是接口(具体的实现类)。
用这种写法不但完成了天然的单例模式,而且真正的符合了开闭原则,引入新的策略,完全不需要修改任何一行旧代码,自认为这种写法是最优雅、最迷人的。
END
策略模式、策略模式与Spring的碰撞的更多相关文章
- 《设计模式面试小炒》策略和工厂模式替代业务场景中复杂的ifelse
<设计模式面试小炒>策略和工厂模式替代业务场景中复杂的ifelse 我是肥哥,一名不专业的面试官! 我是囧囧,一名积极找工作的小菜鸟! 囧囧表示:小白面试最怕的就是面试官问的知识点太笼统, ...
- 3.js模式-策略模式
1. 策略模式 策略模式定义一系列的算法,把它们封装起来,并且可以互相替换. var strategies = { isNonEmpty: function(value,errMsg){ if(val ...
- 模板模式与策略模式/template模式与strategy模式/行为型模式
模板模式 模版模式,又被称为模版方法模式,它可以将工作流程进行封装,并且对外提供了个性化的控制,但主流程外界不能修改,也就是说,模版方法模式中,将工作的主体架构规定好,具体类可以根据自己的需要,各自去 ...
- 命令模式 & 策略模式 & 模板方法
一.策略模式 策略模式:封装易变化的算法,可互相替换. GoF<设计模式>中说道:定义一系列算法,把它们一个个封装起来,并且使它们可以相互替换.该模式使得算法可独立于它们的客户变化. 比如 ...
- Java设计模式(七)策略模式 模板模式
(十三)策略模式 策略图案限定了多个封装算法,该算法可以相互替换包.法的客户.借用还有一位大神的样例. interface ICalculator{ public int calculate(Stri ...
- Springboot中实现策略模式+工厂模式
策略模式和工厂模式相信大家都比较熟悉,但是大家有没有在springboot中实现策略和工厂模式? 具体策略模式和工厂模式的UML我就不给出来了,使用这个这两个模式主要是防止程序中出现大量的IF ELS ...
- 工厂模式&策略模式。
抽象.封装,具体事情做得越多,越容易犯错误.这每个做过具体工作的人都深有体会,相反,官做得越高,说出的话越抽象越笼统,犯错误可能性就越少.好象我们从编程序中也能悟出人生道理.(百度百科) 不断抽象封装 ...
- 简单工厂模式&策略模式-简介与区别
不得不说,这两种模式真的很像. 相似点:都用到了面向对象的继承.多态.抽象,都拥有相似的结构. 不同点:工厂模式仅提供具体的实例对象,怎么使用这个对象是client的自由,策略模式client可以通过 ...
- SpringBoot使用策略模式+工厂模式
为了防止大量的if...else...或switch case代码的出现,可以使用策略模式+工厂模式进行优化. 在我的项目当中,报表繁多,所以尝试了这种方式进行优化报表的架构.代码很简单,如下: Fa ...
随机推荐
- Redis 入门到分布式 (二)API的理解和使用
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 内容: 通用命令 单线程架构 数据结构和内部编码 一.常用的通用命令: keys 计算所有的 ...
- Java 第十一届 蓝桥杯 省模拟赛 最大的元素距离
在数列 a_1, a_2, -, a_n中,定义两个元素 a_i 和 a_j 的距离为 |i-j|+|a_i-a_j|,即元素下标的距离加上元素值的差的绝对值,其中 |x| 表示 x 的绝对值. 给定 ...
- Java实现蓝桥杯凑算式(全排列)
题目6.凑算式 凑算式 B DEF A + - + ------- = 10 C GHI (如果显示有问题,可以参见[图1.jpg]) 这个算式中AI代表19的数字,不同的字母代表不同的数字. 比如: ...
- Android如何使用SQLlite数据库
先写一个类,继承自SQLiteOpenHelper public class MyDatabaseHelper extends SQLiteOpenHelper { //声明一个Context pri ...
- java代码(12) ---CollectionUtils工具类
CollectionUtils工具类 CollectionUtils工具类是在apache下的,而不是springframework下的CollectionUtils 个人觉得在真实项目中Collec ...
- layui导出表格的两种方法
一.不熟悉layui小白使用方法 1.引入如下js文件: 2.编写如下函数: 3.表格ID要与函数取值保持一致即可,再就是自定义一个按钮触发事件 二.引入插件使用方法 1.layui官网下载插件包: ...
- 【Spring注解驱动开发】在@Import注解中使用ImportSelector接口导入bean
写在前面 在上一篇关于Spring的@Import注解的文章<[Spring注解驱动开发]使用@Import注解给容器中快速导入一个组件>中,我们简单介绍了如何使用@Import注解给容器 ...
- 轻量级进度条 – Nprogress.js
进度条库是前端中常见的库之一,bootstrap中提供了多种进度条样式.NProgress.js和nanobar.js是两款轻量级的进度条组件,使用简便. 官网: NProgress.js:http: ...
- api请求允许跨域的问题
让api请求允许跨域 header("Access-Control-Allow-Origin:*");header('Access-Control-Allow-Credential ...
- min_25筛入门
目录 1.什么是min_25筛 2.前置知识 2.1.数论函数 2.2.埃拉托色尼筛 2.3.欧拉筛 3.min_25筛 3.1.计算质数贡献 3.2.计算总贡献 3.3.实现 4.例题 4.1.[L ...