别再用if-else了,用注解去代替他吧

本文来自谢英豪同窗的投稿,但愿你们读完能有所收获。

策略模式

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

注解实现

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

@Data
public class Order {
    /**
     * 订单来源
     */

    private String source;
    /**
     * 支付方式
     */

    private String payMethod;
    /**
     * 订单编号
     */

    private String code;
    /**
     * 订单金额
     */

    private BigDecimal amount;
    // ...其余的一些字段
}

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

@Service
public class OrderService {

    public void orderService(Order order) {
        if(order.getSource().equals("pc")){
            // 处理pc端订单的逻辑
        }else if(order.getSource().equals("mobile")){
            // 处理移动端订单的逻辑
        }else {
            // 其余逻辑
        }
    }
}

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


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


public interface OrderHandler {
    void handle(Order order);
}

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

@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,并在OrderService.orderService方法中,经过策略(订单来源)去决定选择哪个OrderHandler去处理订单。咱们能够这样作:ide

@Service
public class OrderService {

    private Map<String, OrderHandler> orderHandleMap;

    @Autowired
    public void setOrderHandleMap(List<OrderHandler> orderHandlers) {
        // 注入各类类型的订单处理类
        orderHandleMap = orderHandlers.stream().collect(
                Collectors.toMap(orderHandler -> AnnotationUtils.findAnnotation(orderHandler.getClass(), OrderHandlerType.class).source(),
                        v -> v, (v1, v2) -> v1));
    }

    public void orderService(Order order) {
        // ...一些前置处理

        // 经过订单来源肯定对应的handler
        OrderHandler orderHandler = orderHandleMap.get(order.getSource());
        orderHandler.handle(order);

        // ...一些后置处理
    }
}

在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 <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&trade; Language Specification</cite>.
 *
 * 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
 */

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(
            Collectors.toMap(orderHandler -> AnnotationUtils.findAnnotation(orderHandler.getClass(), OrderHandlerType.class),
                    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 OrderHandlerTypeImpl(order.getSource(), order.getPayMethod());
    OrderHandler orderHandler = orderHandleMap.get(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方法:
private int hashCodeImpl() {
    int var1 = 0;
    Entry var3;
    for(Iterator var2 = this.memberValues.entrySet().iterator(); var2.hasNext(); var1 += 127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCode(var3.getValue())) {
        var3 = (Entry)var2.next();
    }
    return var1;
}

private Boolean equalsImpl(Object var1) {
    if (var1 == this) {
        return true;
    } else if (!this.type.isInstance(var1)) {
        return false;
    } else {
        Method[] var2 = this.getMemberMethods();
        int var3 = var2.length;
        for(int var4 = 0; var4 < var3; ++var4) {
            Method var5 = var2[var4];
            String var6 = var5.getName();
            Object var7 = this.memberValues.get(var6);
            Object var8 = null;
            AnnotationInvocationHandler var9 = this.asOneOfUs(var1);
            if (var9 != null) {
                var8 = var9.memberValues.get(var6);
            } else {
                try {
                    var8 = var5.invoke(var1);
                } catch (InvocationTargetException var11) {
                    return false;
                } catch (IllegalAccessException var12) {
                    throw new AssertionError(var12);
                }
            }
            if (!memberValueEquals(var7, var8)) {
                return false;
            }
        }
        return true;
    }
}

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

public class OrderHandlerTypeImpl implements OrderHandlerType {

    // …省略

    @Override
    public int hashCode() {
        int hashCode = 0;
        hashCode += (127 * "source".hashCode()) ^ source.hashCode();
        hashCode += (127 * "payMethod".hashCode()) ^ payMethod.hashCode();
        return hashCode;
    }


    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof OrderHandlerType)) {
            return false;
        }
        OrderHandlerType other = (OrderHandlerType) obj;
        return source.equals(other.source()) && payMethod.equals(other.payMethod());
    }
}

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


这样以来,无论之后业务怎么发展,OrderService核心逻辑不会改变,只须要扩展OrderHandler便可。

若是你们以为这篇文章对你有帮助,你的关注和转发是对我最大的支持,O(∩_∩)O:



本文分享自微信公众号 - 咖啡拿铁(close_3092860495)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索