策略模式+註解 幹掉業務代碼中冗餘的if else…

  • 2019 年 10 月 3 日
  • 筆記

前言:

之前寫過一個工作中常見升級模式-策略模式 的文章,裏面講了具體是怎樣使用策略模式去抽象現實中的業務代碼,今天來拿出實際代碼來寫個demo,這裡做個整理來加深自己對策略模式的理解。
 

一、業務流程

取消訂單會有多種情況,比如:取消即時訂單、取消預約訂單、取消拼車訂單
 
一般業務代碼中我們會根據取消類型來進行不同的邏輯處理,代碼中無外乎多了很多if else的業務邏輯,且代碼耦合度很高。
 
那麼有沒有一種優雅的處理方式呢? 當然有了,現在就來說下我們系統中是如何處理這種問題的( 具體業務需求可以參考我上一篇文章:https://www.cnblogs.com/wang-meng/p/11457544.html
 

二、流程圖

上面已經說了業務流程,當然有流程圖才是最直觀的,這裡我們可以直接參見下圖:
 
 
這裡我們可以使用Spring 獲取到所有 AbstractOrderStrategy 實現類中@OrderTypeAnnotation註解的類,然後比較orderType即可。下面就看具體代碼吧;
 

三、代碼實現及解析

1、自定義訂單類型枚舉:OrderTypeAnnotation
 
1 @Target({ElementType.TYPE})  2 @Retention(RetentionPolicy.RUNTIME)  3 public @interface OrderTypeAnnotation {  4     OrderTypeEnum orderType();  5 }

這裡 ElementType.TYPE表示用於類上,RetentionPolicy.RUNTIME 表示運行時解析的。更多的含義大家可以看註解的相關知識。
 
2、構建OrderType枚舉:OrderTypeEnum
 1 public enum OrderTypeEnum {   2     INSTANT(1, "即時訂單"),   3     BOOKING(2, "預約訂單"),   4     CARPOOL(3, "拼車訂單");   5   6   7     private int code;   8     private String desc;   9  10  11     OrderTypeEnum(int code, String desc) {  12         this.code = code;  13         this.desc = desc;  14     }  15  16  17     public int getCode() {  18         return code;  19     }  20  21  22     public String getDesc() {  23         return desc;  24     }  25  26  27     public static OrderTypeEnum getByCode(int code) {  28         for (OrderTypeEnum orderTypeEnum : values()) {  29             if (orderTypeEnum.getCode() == code) {  30                 return orderTypeEnum;  31             }  32         }  33         return null;  34     }  35 }

 

 
這裡會顯示該取消訂單的類型,如過往代碼中所有if 中的條件判斷,這裡用枚舉進行歸納。
 
3、構建抽象策略及策略實現類
 
策略抽象類:AbstractOrderStrategy
 

1 public abstract class AbstractOrderStrategy {  2  3     /**  4      * 策略抽象方法  5      * @param orderDTO  6      */  7     abstract public void process(OrderDTO orderDTO);  8 }

 
為了容易理解,這裡只有一個業務實現類,繼承該抽象類的策略類都會實現具體的業務。
 
預約單處理策略類:BookingOrderStrategy
1 @Service  2 @OrderTypeAnnotation(orderType = OrderTypeEnum.BOOKING)  3 public class BookingOrderStrategy extends AbstractOrderStrategy {  4  5     @Override  6     public void process(OrderDTO orderDTO) {  7         System.out.println("取消預約訂單");  8     }  9 }

 
這裡重點關注orderType,代碼區分具體的執行策略 都是通過這個type去進行處理的。
 
即時單處理策略類:InstantOrderStrategy
 
1 @Service  2 @OrderTypeAnnotation(orderType = OrderTypeEnum.INSTANT)  3 public class InstantOrderStrategy extends AbstractOrderStrategy {  4  5     @Override  6     public void process(OrderDTO orderDTO) {  7         System.out.println("取消即時訂單");  8     }  9 }

 
4、策略分發處理類
 
這個類主要是接收業務請求,然後轉發到具體的策略類進行處理,這裡使用到了spring 獲取具體的類,然後通過類上面的註解信息進行轉發。
 1 @Service   2 @Slf4j   3 public class CancelOrderStrategyService {   4     /**   5      * 處理取消邏輯   6      */   7     public void process(OrderDTO orderDTO) {   8         Map<String, AbstractOrderStrategy> beanMap = SpringBeanUtils.getBeanMap(AbstractOrderStrategy.class);   9         try {  10             for (Map.Entry<String, AbstractOrderStrategy> entry : beanMap.entrySet()) {  11                 Object real = SpringBeanUtils.getTarget(entry.getValue());  12                 OrderTypeAnnotation annotation = real.getClass().getAnnotation(OrderTypeAnnotation.class);  13                 if (orderDTO.getServiceType() == annotation.orderType().getCode()) {  14                     entry.getValue().process(orderDTO);  15                     break;  16                 }  17             }  18         } catch (Exception e) {  19             log.error("獲取目標代理對象失敗:{}", e);  20         }  21     }  22 }

 

其中:
Map<String, AbstractOrderStrategy> beanMap = SpringBeanUtils.getBeanMap(AbstractOrderStrategy.class);

代表獲取AbstractOrderStrategy 下全部子類或接口。
 
Object real = SpringBeanUtils.getTarget(entry.getValue());

代表獲取具體的代理類
 
OrderTypeAnnotation annotation = real.getClass().getAnnotation(OrderTypeAnnotation.class);

代表取類上有OrderTypeAnnotation 的註解信息。
 
CancelOrderStrategyService.process() 這個方法是可以擴展的,通過外部傳入的class信息來獲取具體的代理類。
 
 
5、Spring獲取bean工具類
 1 @Component   2 public class SpringBeanUtils implements ApplicationContextAware {   3     private static ApplicationContext applicationContext;   4   5     @Override   6     public void setApplicationContext(ApplicationContext context) throws BeansException {   7         if (applicationContext == null) {   8             applicationContext = context;   9         }  10     }  11  12     public static Object getBean(String name) {  13         return applicationContext.getBean(name);  14     }  15  16     public static <T> T getBean(Class<T> clazz) {  17         return applicationContext.getBean(clazz);  18     }  19  20     /**  21      * 獲取類型為requiredType的Map  22      *  23      * @param clazz  24      * @return  25      */  26     public static <T> Map<String, T> getBeanMap(Class<T> clazz) {  27         return applicationContext.getBeansOfType(clazz);  28     }  29  30     /**  31      * 獲取 目標對象  32      *  33      * @param proxy 代理對象  34      * @return 目標對象  35      * @throws Exception  36      */  37     public static Object getTarget(Object proxy) throws Exception {  38         if (!AopUtils.isAopProxy(proxy)) {  39             // 不是代理對象,直接返回  40             return proxy;  41         }  42  43         if (AopUtils.isJdkDynamicProxy(proxy)) {  44             return getJdkDynamicProxyTargetObject(proxy);  45         } else {  46             // cglib  47             return getCglibProxyTargetObject(proxy);  48         }  49     }  50  51     private static Object getJdkDynamicProxyTargetObject(Object proxy) throws Exception {  52         Field field = proxy.getClass().getSuperclass().getDeclaredField("h");  53         field.setAccessible(true);  54         AopProxy aopProxy = (AopProxy) field.get(proxy);  55         Field advised = aopProxy.getClass().getDeclaredField("advised");  56         advised.setAccessible(true);  57  58         Object target = ((AdvisedSupport) advised.get(aopProxy)).getTargetSource().getTarget();  59         return target;  60     }  61  62     private static Object getCglibProxyTargetObject(Object proxy) throws Exception {  63         Field field = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0");  64         field.setAccessible(true);  65         Object dynamicAdvisedInterceptor = field.get(proxy);  66  67         Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised");  68         advised.setAccessible(true);  69  70         Object target = ((AdvisedSupport) advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget();  71         return target;  72     }  73 }

 

 
6、添加單元測試
public class CancelAbstractOrderStrategyTest extends BaseTest {      @Autowired      private CancelOrderStrategyService cancelOrderStrategyService;          @Test      public void process() {          OrderDTO orderDTO = new OrderDTO();          orderDTO.setServiceType(OrderTypeEnum.INSTANT.getCode());          cancelOrderStrategyService.process(orderDTO);      }  }

 
7、打印結果:
1 取消即時訂單

 
到了這裡代碼就已經寫完了,如果仔細看的話 應該會明白這裡的設計思路,通過策略模式+註解 可以大大的降低業務的耦合度,而且也極大的方便了後期維護的工作量。
 
部分代碼參考:https://www.cnblogs.com/HelloDeveloper/p/11390512.html, 這裡對核心類做了修改和精簡。