文档章节

策略模式、策略模式与Spring的碰撞

o
 osc_yo7hxxom
发布于 07/01 12:17
字数 2588
阅读 19
收藏 0

精选30+云产品,助力企业轻松上云!>>>

策略模式是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图:

image.png

 

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就是接口(具体的实现类)。

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

总结

文章是有点水,请轻喷🤦‍♂️

如果你觉得这篇文章不错,请别忘记点个关注哦~😊

 

o
粉丝 0
博文 59
码字总数 0
作品 0
私信 提问
加载中
请先登录后再评论。
策略模式、策略模式与Spring的碰撞

策略模式是GoF23种设计模式中比较简单的了,也是常用的设计模式之一,今天我们就来看看策略模式。 实际案例 我工作第三年的时候,重构旅游路线的机票查询模块,旅游路线分为四种情况: 如果A...

osc_vcgg4s9o
07/01
7
0
策略模式、策略模式与Spring的碰撞

策略模式是GoF23种设计模式中比较简单的了,也是常用的设计模式之一,今天我们就来看看策略模式。 实际案例 我工作第三年的时候,重构旅游路线的机票查询模块,旅游路线分为四种情况: 如果A...

CodeBear
06/30
0
0
spring 中常用的设计模式

一、 Spring 中常见的设计模式 工厂模式 : BeanFactory 装饰器模式: BeanWrapper 代理模式: AopProxy 单例模式: ApplicationContext 委派模式: DispatcherServlet 策略模式: HandlerMa...

osc_bq1qsg6s
2019/05/21
26
0
漫谈 GOF 设计模式在 Spring 框架中的实现

原文地址:梁桂钊的博客 博客地址:http://blog.720ui.com 欢迎关注公众号:「服务端思维」。一群同频者,一起成长,一起精进,打破认知的局限性。 漫谈 GOF 设计模式在 Spring 框架中的实现...

osc_0vxxme2b
04/16
1
0
漫谈 GOF 设计模式在 Spring 框架中的实现

原文地址:梁桂钊的博客 博客地址:http://blog.720ui.com 欢迎关注公众号:「服务端思维」。一群同频者,一起成长,一起精进,打破认知的局限性。 漫谈 GOF 设计模式在 Spring 框架中的实现...

梁桂钊
2019/10/09
582
0

没有更多内容

加载失败,请刷新页面

加载更多

Kafka如何在千万级别时优化JVM GC问题?

大家都知道Kafka是一个高吞吐的消息队列,是大数据场景首选的消息队列,这种场景就意味着发送单位时间消息的量会特别的大,那既然如此巨大的数据量,kafka是如何支撑起如此庞大的数据量的分发...

hummerstudio
06/18
6
0
我打赌!90%程序员都破解不了这个粽子,不信你试!

放假了 各位读者朋友们,马上就是端午小长假啦,开心激动有木有? 新的故事文章还在创作中,写了初稿感觉不太满意又推倒重来。其实写故事还是挺难的,读者可能第一次第二次有新鲜感,写多了就...

轩辕之风
06/24
11
0
如何删库跑路?教你使用Binlog日志恢复误删的MySQL数据

前言 “删库跑路”是程序员经常谈起的话题,今天,我就要教大家如何删!库!跑!路! 开个玩笑,今天文章的主题是如何使用Mysql内置的Binlog日志对误删的数据进行恢复,读完本文,你能够了解...

后端技术漫谈
01/14
22
0
PHP设计模式之代理模式

PHP设计模式之代理模式 代理人这个职业在中国有另外一个称呼,房产经济人、保险经济人,其实这个职业在国外都是叫做房产代理或者保险代理。顾名思义,就是由他们来帮我们处理这些对我们大部分...

硬核项目经理
2019/09/23
7
0
Redis的复制模式

Redis的复制功能分为同步(sync)和命令传播(command propagate)两个操作。 同步 同步操作用于将从服务器的数据库状态更新至主服务器当前所处的数据库状态。 1. 旧版本的执行步骤 从服务器...

osc_s9cni3go
17分钟前
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部