策略模式是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;}@Overridepublic String toString() {return "Ticket{" +"desc='" + desc + '\'' +'}';}
}public interface QueryTicketService {List<Ticket> getTicketList();
}public class QueryTicketAService implements QueryTicketService {@Overridepublic List<Ticket> getTicketList() {List<Ticket> list = new ArrayList<>();list.add(new Ticket("去程机票"));list.add(new Ticket("返程机票"));return list;}
}public class QueryTicketBService implements QueryTicketService {@Overridepublic 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 {@Overridepublic 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 {@Overridepublic 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> {@Overridepublic 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 {@Overridepublic List<Ticket> getTicketList() {List<Ticket> list = new ArrayList<>();list.add(new Ticket("去程机票"));list.add(new Ticket("返程机票"));return list;}
}@Service
public class QueryTicketDConcreteStrategy implements QueryTicketStrategy {@Overridepublic 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 {@Autowiredprivate QueryTicketStrategy queryTicketAConcreteStrategy;@Autowiredprivate QueryTicketStrategy queryTicketDConcreteStrategy;private static Map<Integer, QueryTicketStrategy> map = new HashMap<>();@PostConstructpublic 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 {@Overridepublic 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 {@Overridepublic 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 {@Autowiredprivate 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就是接口(具体的实现类)。

用这种写法不但完成了天然的单例模式,而且真正的符合了开闭原则,引入新的策略,完全不需要修改任何一行旧代码,自认为这种写法是最优雅、最迷人的。

总结

文章是有点水,请轻喷

策略模式、策略模式与Spring的碰撞相关推荐

  1. 如何使用 Spring 实现策略模式+工厂模式

    欢迎关注方志朋的博客,回复"666"获面试宝典 一.策略模式 策略模式定义了一组算法,将每个算法都封装起来,并且使它们之间可以互换 1.策略模式主要角色 主要角色如下: 封装角色( ...

  2. 实践:使用Spring 原生注解来快速实现 策略模式 + 工厂模式

    作者:Richard_Yi juejin.im/post/5db0e910518825648f2ef355 前言 这阵子在做项目组重构的工作,工作中的一部分就是就目前代码库中与企业交互的逻辑抽离出来, ...

  3. 代理模式 委派模式 策略模式_委派模式和策略模式

    一.委派模式 委派模式(Delegate Pattern):指负责任务的调度和分配任务,跟代理模式很像,可以看做是一种特殊情况下的静态代理的全权代理,但是代理模式注重过程,而委派模式注重结果.(属于行 ...

  4. 设计模式之策略模式+工厂模式+模板模式结合

    设计模式之策略模式+模板模式 为什么总是学不好设计模式 从"登录功能"中发现问题. 首先我们简单的了解功能需求: 于是你开始干活了: 1.控制层代码如下,根据不同的登录方式调用不同 ...

  5. 【设计模式】策略模式+工厂模式动态绑定类名的几种方式

    策略模式说明 在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改.这种类型的设计模式属于行为型模式. 在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象 ...

  6. 进阶学习之旅-设计模式之(委派模式策略模式)

    文章目录 1.课程学习目标 2.内容定位 3.委派模式详解 3.1委派模式的定义 3.2 demo案例 3.2.1模拟Boss指派任务给Leader 由员工完成任务执行 3.2.2 模拟spring ...

  7. 策略模式+工厂模式的组合使用

    策略模式+工厂模式的组合使用 策略模式:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换. 个人感觉特别好用,当你在一段代码中,有很多的候选算法,你就可以用这个策略模式了,可以有效的取出 ...

  8. springboot 使用工厂模式+策略模式替代多重if 案例

    项目背景: 由于做的是物联网项目,现在需要实现的是网关入网+子设备注册:网关有3个逻辑,分别为首次入网.解绑后同一个人入网(恢复).解绑后换人入网(换人).子设备注册: 原先写法是: if(type ...

  9. 设计模式 — 行为型模式 — 策略模式

    目录 文章目录 目录 策略模式 应用场景 代码示例 策略模式 策略模式中,首先定义了一系列不同的算法,并把它们一一封装起来,然后在策略类中,使这些算法可以相互替换.这意味着,让一个类的行为(算法)可以 ...

最新文章

  1. 图灵奖获得者 Alan Kay:突破常规思维,创建下一代科研社区(附视频)
  2. Kotlin中的接口回调
  3. CentOS中配置Mysql表名忽略大小写以及提示:Caused by: org.quartz.impl.jdbcjobstore.LockException: Failure obtaining d
  4. 优惠券领取--Java电商
  5. Java基础学习总结(105)——让 Java 开发更简单,提高工作效率!
  6. 前端内容占位技术分享
  7. 台达AS228Tplc加台达触摸屏一套程序,一共100个io 左右,一个伺服程序。plc程序有FB块,ST语言,C语言,触摸屏有配方和数据储存功能
  8. S3存储服务间数据同步工具Rclone迁移教程
  9. Android识别图片中的颜色
  10. 纵享丝滑滑动切换的周月日历,水滴效果,可高度定制,仿小米日历
  11. 国内期货期权保证金计算方式
  12. 懒人笔记—python基础语法1
  13. WIN10下配置Yolov3(VS2019,GPU)+opencv训练自己的数据集(绝对详细,小白型记录)
  14. python批量添加姓名生成奖状批量处理图片教师学生奖状
  15. ubuntu 常见错误--Could not get lock /var/lib/dpkg/lock
  16. 超表面学习一 初步印象
  17. 小米mix2鸿蒙系统,小米MIX 2配置详解:小米终于有了8GB内存
  18. js 获取url参数的方法
  19. 毕业设计 - java web 酒店管理系统的设计与实现【源码+论文】
  20. Win10安装配置Spark3.0.2+单机版hadoop+JDK并运行实例+安装运行Docker

热门文章

  1. 如果你有一个ods,里面需要放进去2个country的信息:ship-to coutry和 sold-to country,你怎么做?
  2. Linux系统之间的文件共享
  3. windows 架设trac 服务器 -==- 集成subversion 和apache
  4. 硬件工程师都应该DIY一个示波器
  5. 爱立信忙收5G专利费;中兴助力LPWAN物联网商用| IoT黑板报
  6. java 编写 欢迎你_Java第三章 P72 输出”欢迎你,青“ 否则输出”对不起,你不是青“...
  7. testbench产生同频不同相位的时钟信号
  8. 职场提升的10条捷径
  9. mysql unit类型_mysql入门-数据类型(二)
  10. 卫生纸玫瑰花折法5步_手工折纸:[12]玫瑰花的折法