来自公众号:咖啡拿铁

策略模式

经常在网上看到一些名为“别再if-else走天下了”,“教你干掉if-else”等之类的文章,大部分都会讲到用策略模式去代替if-else。策略模式实现的方式也大同小异。主要是定义统一行为(接口或抽象类),并实现不同策略下的处理逻辑(对应实现类)。客户端使用时自己选择相应的处理类,利用工厂或其他方式。

注解实现

本文要说的是用注解实现策略模式的方式,以及一些注意点。
话不多说,还是以最常 见的订单处理为例。首先定义这样一个订单实体类:

@Data

假如对于不同来源(pc端、移动端)的订单需要不同的逻辑处理。项目中一般会有OrderService这样一个类,如下,里面有一坨if-else的逻辑,目的是根据订单的来源的做不同的处理。

@Service

策略模式就是要干掉上面的一坨if-else,使得代码看起来优雅且高大上。
现在就让我们开始干掉这一坨if-else。先总览下结构:


1.首先定义一个OrderHandler接口,此接口规定了处理订单的方法。

public 

2.定义一个OrderHandlerType注解,来表示某个类是用来处理何种来源的订单。

@Target(ElementType.TYPE)

3.接下来就是实现pc端和移动端订单处理各自的handler,并加上我们所定义的OrderHandlerType注解。

@OrderHandlerType(source = 

4.以上准备就绪后,就是向spring容器中注入各种订单处理的handler,并在OrderService.orderService方法中,通过策略(订单来源)去决定选择哪一个OrderHandler去处理订单。我们可以这样做:

@Service

在OrderService中,维护了一个orderHandleMap,它的key为订单来源,value为对应的订单处理器Handler。通过@Autowired去初始化orderHandleMap(这里有一个lambda表达式,仔细看下其实没什么难度的)。这样一来,OrderService.orderService里的一坨if-else不见了,取而代之的仅仅是两行代码。即,先从orderHandleMap中根据订单来源获取对应的OrderHandler,然后执行OrderHandler.handle方法即可。
这种做法的好处是,不论以后业务如何发展致使订单来源种类增加,OrderService的核心逻辑不会改变,我们只需要实现新增来源的OrderHandler即可,且团队中每人开发各自负责的订单来源对应的OrderHandler即可,彼此间互不干扰。

到此,似乎已经讲完了通过注解实现策略模式,干掉if-else的方法,就这样结束了吗?不,真正的重点从现在才开始。

现在回过头看orderHandleMap这个Map,它的key是订单来源,假如,我们想通过订单来源+订单支付方式这两个属性来决定到底使用哪一种OrderHandler怎么办?比如PC端支付宝支付的订单是一种处理逻辑(PCAliPayOrderHandler),PC端微信支付的订单是另外一种处理逻辑(PCWeChatOrderHandler),对应的还有移动端支付宝支付(MobileAliPayOrderHandler)和移动端微信支付(MobileWeChatOrderHandler)。

这时候我们的key应该存什么呢,可能有人会说,我直接存订单来源+订单支付方式组成的字符串不就行了吗?确实可以,但是如果这时业务逻辑又变了(向pm低头),PC端支付宝支付和微信支付是同一种处理逻辑,而移动端支付宝支付和微信支付是不同的处理逻辑,那情况就变成了PCAliPayOrderHandler和PCWeChatOrderHandler这两个类是同一套代码逻辑。我们干掉了if-else,但却造出了两份相同的代码,这是一个作为有强迫症的程序员所不能容忍的。怎么干掉这两个逻辑相同的类呢?

首先,我们可以回顾下,注解它究竟是个什么玩意?不知道大家有没有注意到定义注解的语法,也就是@interface,与定义接口的语法想比,仅仅多了一个@。翻看jdk,可以找到这么一个接口Annotation,如下

/** * The common interface extended by all annotation types.  Note that an * interface that manually extends this one does not define * an annotation type.  Also note that this interface does not itself * define an annotation type. * * More information about annotation types can be found in section 9.6 of * The Java™ Language Specification. * * The {@link java.lang.reflect.AnnotatedElement} interface discusses * compatibility concerns when evolving an annotation type from being * non-repeatable to being repeatable. * * @author  Josh Bloch * @since   1.5 */

开头就表明了,The common interface extended by all annotation types。说的很明白了,其实注解它就是个接口,对,它就是个接口而已,@interface仅仅是个语法糖。那么,注解既然是个接口,就必然会有相应的实现类,那实现类哪里来呢?上述中我们仅仅定义了OrderHandlerType注解,别的什么也没有做。这时候不得不提动态代理了,一定是jdk在背后为我们做了些什么。
为了追踪JVM在运行过程中生成的JDK动态代理类。我们可以设置VM启动参数如下:

true

该参数可以保存所生成的JDK动态代理类到本地。额外说一句,若我们想追踪cglib所生成的代理类,即对应的字节码文件,可以设置参数:

"保存的路径");

添加参数后再次启动项目,可以看到我们项目里多了许多class文件(这里只截取了部分,笔者启动的项目共生成了97个动态代理类。由于我的项目是springboot环境,因此生成了许多如ConditionalOnMissingBean、Configuration、Autowired等注解对应的代理类):


那接下来就是要找到我们所关心的,即jdk为我们自定义的OrderHandlerType注解所生成的代理类。由于jdk生成的动态代理类都会继承Proxy这个类,而java又是单继承的,所以,我们只需要找到实现了OrderHandlerType的类即可。遍历这些类文件,发现$Proxy63.class实现了我们定义的OrderHandlerType注解:

public 

我们知道,jdk动态代理其实现的核心是:


也就是这个构造函数的InvocationHandler对象了。那么注解的InvocationHandler是哪个具体实现呢?不难发现就是:


这个类里面的核心属性,就是那个 memberValues,我们在使用注解时给注解属性的赋值,都存储在这个map里了。而代理类中的各种方法的实现,实际上是调用了 AnnotationInvocationHandler 里的 invoke 方法。
好了,现在我们知道了注解就是个接口,且通过动态代理实现其中所定义的各种方法。那么回到我们的OrderService,为什么不把key的类型设置为OrderHandlerType?就像这样。

private Map

如此一来,不管决定订单处理器orderhandler的因素怎么变,我们便可以以不变应万变(这不就是我们所追求的代码高扩展性和灵活性么)。那当我们的map的key变成了OrderHandlerType之后,注入和获取的逻辑就要相应改变,注入的地方很好改变,如下:

public 

那获取的逻辑要怎么实现?我们怎么根据order的来源和支付方式去orderHandleMap里获取对应的OrderHandler呢?问题变成了如何关联order的来源和支付方式与OrderHandlerType注解。
还记得刚才所说的注解就是个接口吗,既然是个接口,我们自己实现一个类不就完事了么,这样就把order的来源和支付方式与OrderHandlerType注解关联起来了。说干就干,现在我们有了这么一个类,

public 

在获取对应OrderHandler时我们可以这样写,

public void orderService(Order order) {

看起来没什么问题了,来运行一下。不对劲啊,空指针,那个异常它来了。


我们断点打在NPE那一行,


一定是姿势不对,漏掉了什么。那我们就来分析下。orderHandleMap中确实注入了所定义的几个OrderHandler(PCAliPayOrderHandler、PCWeChatOrderHandler、MobileAliPayOrderHandler、MobileWeChatOrderHandler),但是get却没有获取到,这是为什么呢?我们来回忆下,map的get方法逻辑,还记得最开始学习java时的hashCode和equals方法吗?
不知道大家注意到没有,map中key对应的类型是$Proxy63(jdk动态代理给我们生成的),跟我们自己实现的OrderHandlerTypeImpl是不同类型的。
梳理下,在Autowied时,我们放进orderHandleMap的key是动态代理类对象,而获取时,自定义了OrderHandlerTypeI实现类OrderHandlerTypeImpl,而又没有重写hashCode和equals方法,才导致从map中没有获取到我们所想要的OrderHandler,那么,我们把实现类OrderHandlerTypeImpl的hashCode和equals这两个方法重写,保持跟动态代理类生成的一样不就行了吗?再回看下动态代理给我们生成的这两个方法,前面说了,注解对应的代理类方法调用实际上都是AnnotationInvocationHandler里面的方法,翻看AnnotationInvocationHandler里面的hashCode和equals方法:

private int hashCodeImpl() {

具体的逻辑也比较简单,就不分析了。那我们就按照AnnotationInvocationHandler中的实现,在我们的OrderHandlerTypeImpl中按照相同的逻辑重写下这两个方法,如下

public 

再次运行看看是否达到我们预期,果不其然,这次可以正常获取到了handler,至此,大功告成。


这样以来,不管以后业务怎么发展,OrderService核心逻辑不会改变,只需要扩展OrderHandler即可。

如果大家觉得这篇文章对你有帮助,你的关注和转发是对我最大的支持,O(∩_∩)O:


●编号1299,输入编号直达本文

●输入m获取文章目录

程序员求职面试

分享程序员找工作经验

程序员笔试、面试题

@order注解_别再用ifelse了,用注解去代替他吧相关推荐

  1. 怎么实现注解_通透!一口气搞懂注解到底怎么用

    日志脱敏场景简介 在日志里我们的日志一般打印的是 model 的 Json string,比如有以下 model 类 public class Request { /** * 用户姓名 */ priv ...

  2. 自定义JAVA注解_深入理解Java:自定义java注解

    要深入学习注解,我们就必须能定义自己的注解,并使用注解,在定义自己的注解之前,我们就必须要了解Java为我们提供的元注解和相关定义注解的语法. 元注解: 元注解的作用就是负责注解其他注解.Java5. ...

  3. java nullable注解_【Java】idea @NotNull @Nullable 注解

    这两个注解在idea里面可以帮助我们检测方法的返回值,方法参数以及局部变量是否为空,从而帮助我们减少一些NPE的发生. 1. 原始注解 @NotNull @Nullable最开始只能使用idea提供的 ...

  4. @autowired注解 抽象类_别再用ifelse了,用注解去代替他吧

    来自公众号:咖啡拿铁 策略模式 经常在网上看到一些名为"别再if-else走天下了","教你干掉if-else"等之类的文章,大部分都会讲到用策略模式去代替if ...

  5. @param注解_启用 parameters 编译选项简化 mybatis @Param 注解重复问题

    在使用 mybatis 查询的时候, 只需要定义一个查询接口, mybatis 会为我们注入注解实现或是 xml 实现. 但当我们需要传递参数时, 通常需要 @Param 来定义一个名称, 但经常的, ...

  6. spring mysql 注解_【Spring】SpringMVC之基于注解的实现SpringMVC+MySQL

    目录结构: contents structure [-] SpringMVC是什么 MVC的全称是Model View Controller,通过实现MVC框架可以很好的数据.视图.业务逻辑进行分离. ...

  7. loginrequired注解_简单实现一个登录验证的注解来保护私有资源

    背景 自定义注解标注受保护的资源访问,当要访问的url被标注了@LoginRequied的时候就变成了一个受保护的资源,需要用户登录或者更进一步需要用户拥有某个权限才能操作.本项目使用的springb ...

  8. 三字经带注解_《三字经》带拼音和注解完美打印版

    rén zhī chū xìng běn shàn xìng xiāng jìn xí xiāng yu ǎ n 人 之 初 性 本 善 性 相 近 习 相 远 [解释]人生下来的时候都是好的,只是由 ...

  9. 注解(7)_元注解_元注解的概念_@Retention_@Target_@Documented_@Inherited

    元注解的概念 元注解是用于修饰其他注解的注解. (1)元注解本身也是注解. (2)元注解是用来修饰别的注解的. JDK5.0提供了四种元注解: @Retention @Target @Document ...

最新文章

  1. 王二涛团队及合作者揭示沙棘放线菌固氮生物学机制
  2. wp7上MD5加密类
  3. 高通暂时不会针对物联网打造专属处理器
  4. java velocity是什么意思,什么是Apache Velocity?
  5. NYOJ 679 贪婪的商店
  6. ViewPagerIndicator+viewpager的简单使用,不需要导入Library包
  7. AMD推Radeon HD 7790显卡 性价比突出下月开卖
  8. [剑指offer]面试题35:第一个只出现一次的字符
  9. 图像二值化 php im2bw,图像二值化-MATLAB实现
  10. Linux进程全解6——进程的诞生和消亡
  11. flutter不支持热更新_Flutter 在安卓上可以实现热更新了
  12. 理解C/C++运行时库
  13. 解决浏览器Adobe Flash Player不是最新版本问题
  14. 从APP测试角度看新增长应用类型如何取得优势
  15. 如何获取网页logo与favicon图标使用
  16. win7没有权限使用计算机的,Win7提示你可能没有权限使用网络资源|未授予用户在此计算机解决方法...
  17. y7000电池固件_y7000怎么刷电池固件|Surface Pro 3固件更新:电池续航问题终解决
  18. 第一章:HBase定义
  19. 如何下载VS2005程序到开发板上(总结)
  20. Python实现文件上传和下载

热门文章

  1. 关于C# Span的一些实践
  2. WPF 从 .net core 3.1 到 .net 5.0
  3. 5G发展是绵绵秋雨 应循序渐进
  4. 《Unit Testing》2.1 经典学派如何做测试隔离
  5. 给微软的依赖注入框架写一些扩展方法
  6. VS Code 摸鱼插件开发小记
  7. 程序员过关斩将--cookie和session的关系其实很简单
  8. 使用Minikube部署本地Kubernetes集群(二十九)
  9. 在Windows上使用Docker运行.NetCore
  10. 利用 Azure Functions 实现无服务器体系结构