別再用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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索