策略模式+注解,代替if-else

策略模式+注解,代替if-else
经常在⽹上看到⼀些名为“别再if-else⾛天下了”,“教你⼲掉if-else”等之类的⽂章,⼤部分都会讲到⽤策略模式去代替if-else。策略模式实现的⽅式也⼤同⼩异。主要是定义统⼀⾏为(接⼝或抽象类),并实现不同策略下的处理逻辑(对应实现类)。客户端使⽤时⾃⼰选择相应的处理类,利⽤⼯⼚或其他⽅式。
本⽂要说的是⽤注解实现策略模式的⽅式,以及⼀些注意点。话不多说,还是以最常 见的订单处理为例。⾸先定义这样⼀个订单实体类:
@Data
public class Order {
/**
* 订单来源
*/
private String source;
/**
* ⽀付⽅式
*/
private String payMethod;
/**
* 订单编号
*/
private String code;
/**
* 订单⾦额
*/
private BigDecimal amount;
// ...其他的⼀些字段
}
层压机硅胶板假如对于不同来源(pc端、移动端)的订单需要不同的逻辑处理。项⽬中⼀般会有OrderService这样⼀个类,如下,⾥⾯有⼀坨if-else的逻辑,⽬的是根据订单的来源的做不同的处理。
@Service
public class OrderService {
public void orderService(Order order) {
Source().equals("pc")){
// 处理pc端订单的逻辑
}else Source().equals("mobile")){
// 处理移动端订单的逻辑
}else {
// 其他逻辑
}
}
}
策略模式就是要⼲掉上⾯的⼀坨if-else,使得代码看起来优雅且⾼⼤上。现在就让我们开始⼲掉这⼀坨if-else。先总览下结构:
图⽚
1.⾸先定义⼀个OrderHandler接⼝,此接⼝规定了处理订单的⽅法。
public interface OrderHandler {
void handle(Order order);
}
2.定义⼀个OrderHandlerType注解,来表⽰某个类是⽤来处理何种来源的订单。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Service
public @interface OrderHandlerType {
String source();
}
3.接下来就是实现pc端和移动端订单处理各⾃的handler,并加上我们所定义的OrderHandlerType注解。
@OrderHandlerType(source = "mobile")
public class MobileOrderHandler implements OrderHandler {
@Override电梯井口门
public void handle(Order order) {
System.out.println("处理移动端订单");
}
}
@OrderHandlerType(source = "pc")
public class PCOrderHandler implements OrderHandler {
@Override
public void handle(Order order) {
System.out.println("处理PC端订单");
}
}
4.以上准备就绪后,就是向spring容器中注⼊各种订单处理的handler,并在derService⽅法中,通过策略(订单来源)去决定选择哪⼀个OrderHandler去处理订单。我们可以这样做:
@Service
public class OrderService {
private Map<String, OrderHandler> orderHandleMap;
@Autowired
public void setOrderHandleMap(List<OrderHandler> orderHandlers) {
// 注⼊各种类型的订单处理类
orderHandleMap = orderHandlers.stream().collect(
dlerType.class).source(),
v -> v, (v1, v2) -> v1));
}
public void orderService(Order order) {
// ...⼀些前置处理
// 通过订单来源确定对应的handler
OrderHandler orderHandler = (Source());
orderHandler.handle(order);
// ...⼀些后置处理
}
}
在OrderService中,维护了⼀个orderHandleMap,它的key为订单来源,value为对应的订单处理器Handler。通过@Autowired去初始化orderHandleMap(这⾥有⼀个lambda表达式,仔细看下其实没什么难度的)。这样⼀来,derService⾥的⼀坨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 <i>not</i> 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
* <cite>The Java™ Language Specification</cite>.
*
* The {@link flect.AnnotatedElement} interface discusses
* compatibility concerns when evolving an annotation type from being数字振镜
* non-repeatable to being repeatable.
*
* @author  Josh Bloch
* @since  1.5
*/
public interface Annotation {
// …省略
}
开头就表明了,The common interface extended by all annotation types。说的很明⽩了,其实注解它就是个接⼝,对,它就是个接⼝⽽已,@interface仅仅是个语法糖。那么,注解既然是个接⼝,就必然会有相应的实现类,那实现类哪⾥来呢?上述中我们仅仅定义了OrderHandlerType注解,别的什么也没有做。这时候不得不提动态代理了,⼀定是jdk在背后为我们做了些什么。
为了追踪JVM在运⾏过程中⽣成的JDK动态代理类。我们可以设置VM启动参数如下:
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
该参数可以保存所⽣成的JDK动态代理类到本地。额外说⼀句,若我们想追踪cglib所⽣成的代理类,即对应的字节码⽂件,可以设置参数:System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "保存的路径");
添加参数后再次启动项⽬,可以看到我们项⽬⾥多了许多class⽂件(这⾥只截取了部分,笔者启动的
项⽬共⽣成了97个动态代理类。由于我的项⽬是springboot环境,因此⽣成了许多如ConditionalOnMissingBean、Configuration、Autowired等注解对应的代理类):
图⽚
那接下来就是要到我们所关⼼的,即jdk为我们⾃定义的OrderHandlerType注解所⽣成的代理类。由于jdk⽣成的动态代理类都会继承Proxy这个类,⽽java⼜是单继承的,所以,我们只需要到实现了OrderHandlerType的类即可。遍历这些类⽂件,发现$Proxy63.class 实现了我们定义的OrderHandlerType注解:
public final class $Proxy63 extends Proxy implements OrderHandlerType {
private static Method m1;
private static Method m2;
private static Method m4;
private static Method m3;
private static Method m0;
public $Proxy63(InvocationHandler var1) throws  {
super(var1);
}
// …省略
}
我们知道,jdk动态代理其实现的核⼼是:
也就是这个构造函数的InvocationHandler对象了。那么注解的InvocationHandler是哪个具体实现呢?不难发现就是:
这个类⾥⾯的核⼼属性,就是那个 memberValues,我们在使⽤注解时给注解属性的赋值,都存储在这个map⾥了。⽽代理类中的各种⽅法的实现,实际上是调⽤了 AnnotationInvocationHandler ⾥的 invoke ⽅法。
好了,现在我们知道了注解就是个接⼝,且通过动态代理实现其中所定义的各种⽅法。那么回到我们的OrderService,为什么不把key的类型设置为OrderHandlerType?就像这样。
private Map<OrderHandlerType, OrderHandler> orderHandleMap;
如此⼀来,不管决定订单处理器orderhandler的因素怎么变,我们便可以以不变应万变(这不就是我们所追求的代码⾼扩展性和灵活性么)。那当我们的map的key变成了OrderHandlerType之后,注⼊和获取的逻辑就要相应改变,注⼊的地⽅很好改变,如下:
public class OrderService {
private Map<OrderHandlerType, OrderHandler> orderHandleMap;
@Autowired
public void setOrderHandleMap(List<OrderHandler> orderHandlers) {
// 注⼊各种类型的订单处理类
orderHandleMap = orderHandlers.stream().collect(
v -> v, (v1, v2) -> v1));
}
// ...省略
}
那获取的逻辑要怎么实现?我们怎么根据order的来源和⽀付⽅式去orderHandleMap⾥获取对应的OrderHandler呢?问题变成了如何关联order的来源和⽀付⽅式与OrderHandlerType注解。
还记得刚才所说的注解就是个接⼝吗,既然是个接⼝,我们⾃⼰实现⼀个类不就完事了么,这样就把order的来源和⽀付⽅式与OrderHandlerType注解关联起来了。说⼲就⼲,现在我们有了这么⼀个类,
public class OrderHandlerTypeImpl implements OrderHandlerType {
private String source;
private String payMethod;
OrderHandlerTypeImpl(String source, String payMethod) {
this.source = source;
this.payMethod = payMethod;
}
安全传输@Override
public String source() {
return source;
}
@Override
public String payMethod() {
return payMethod;
}
@Override
public Class<? extends Annotation> annotationType() {
return OrderHandlerType.class;齿槽转矩
}
}
在获取对应OrderHandler时我们可以这样写,
public void orderService(Order order) {
// ...⼀些前置处理
// 通过订单来源确以及⽀付⽅式获取对应的handler
OrderHandlerType orderHandlerType = new Source(), PayMethod());
OrderHandler orderHandler = (orderHandlerType);
orderHandler.handle(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⽅法:

本文发布于:2024-09-22 12:30:15,感谢您对本站的认可!

本文链接:https://www.17tex.com/tex/4/254256.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:订单   注解   实现   处理   来源
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2024 Comsenz Inc.Powered by © 易纺专利技术学习网 豫ICP备2022007602号 豫公网安备41160202000603 站长QQ:729038198 关于我们 投诉建议