策略模式是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. 「SAP技术」SAP MM 不能向被分配了工厂代码的供应商采购服务?
  2. 前端基础21:正则基础
  3. MFC之AfxbeginThread 线程 创建、挂起、释放、结束、退出
  4. as上的git到码云操作_如何使用git从码云克隆项目到本地?
  5. 程序猿 自己所擅长的还是码代码 请远离 业务。
  6. 关于写程序时「对数据库操作」的一些总结。
  7. java泛型T和通配符问号的区别
  8. delphi xe5 android,android – 发送电子邮件Delphi XE5
  9. IOS 本地图片加载
  10. java 从服务器下载文件并保存到本地
  11. xh2.54母头转换为杜邦线母头
  12. 图灵机器人不再免费(19.9元),获取调用api的key,实名认证遇到413 Request Entity Too Large问题
  13. 转载-计算机基础教程之屏蔽软件联网
  14. 在线latex的一些操作
  15. 纸鸢|物联网云平台产品设计思路
  16. win7下 文件名太长无法删除,无法更名问题
  17. 基于WebGIS的电子政务应用(基于J2EE的MVC架构)
  18. k线符号图解大全_八种常见的K线符号
  19. 房产“去库存”释放需求 集成灶市场待挖掘
  20. 转 做自己的m3u8点播系统使用HTTP Live Streaming HLS技术)

热门文章

  1. 微博缩短网址是如何实现的
  2. C语言中有引用吗?reference
  3. php简单的购物车,利用PHP实现一个简单购物车的demo示例代码
  4. Excel一键删除工作表中杂乱无章的所有照片
  5. ros 与 sony ps3joy
  6. 微信视频号+QQ“小世界”,腾讯围剿抖音
  7. 好用的一款文档转换链接(在线免费)
  8. 树莓派4b + python3 + pyzbar + opencv + 摄像头 扫描识别二维码(寻找最大的二维码)
  9. 摩尔定律与阿姆达尔定律
  10. LeetCode短视频 | 最长有效括号使用栈很容易解决,但偏用动态规划