【一起学习设计模式】策略模式实战二:配合注解 干掉业务代码中冗余的if else......

本文主要是介绍【一起学习设计模式】策略模式实战二:配合注解 干掉业务代码中冗余的if else......,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言:

之前写过一个工作中常见升级模式-策略模式 的文章,里面讲了具体是怎样使用策略模式去抽象现实中的业务代码,今天来拿出实际代码来写个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 取消即时订单
到了这里代码就已经写完了,如果仔细看的话 应该会明白这里的设计思路,通过策略模式+注解 可以大大的降低业务的耦合度,而且也极大的方便了后期维护的工作量。

这篇关于【一起学习设计模式】策略模式实战二:配合注解 干掉业务代码中冗余的if else......的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1091322

相关文章

C#实现千万数据秒级导入的代码

《C#实现千万数据秒级导入的代码》在实际开发中excel导入很常见,现代社会中很容易遇到大数据处理业务,所以本文我就给大家分享一下千万数据秒级导入怎么实现,文中有详细的代码示例供大家参考,需要的朋友可... 目录前言一、数据存储二、处理逻辑优化前代码处理逻辑优化后的代码总结前言在实际开发中excel导入很

MyBatis分页查询实战案例完整流程

《MyBatis分页查询实战案例完整流程》MyBatis是一个强大的Java持久层框架,支持自定义SQL和高级映射,本案例以员工工资信息管理为例,详细讲解如何在IDEA中使用MyBatis结合Page... 目录1. MyBATis框架简介2. 分页查询原理与应用场景2.1 分页查询的基本原理2.1.1 分

SpringBoot+RustFS 实现文件切片极速上传的实例代码

《SpringBoot+RustFS实现文件切片极速上传的实例代码》本文介绍利用SpringBoot和RustFS构建高性能文件切片上传系统,实现大文件秒传、断点续传和分片上传等功能,具有一定的参考... 目录一、为什么选择 RustFS + SpringBoot?二、环境准备与部署2.1 安装 RustF

Python实现Excel批量样式修改器(附完整代码)

《Python实现Excel批量样式修改器(附完整代码)》这篇文章主要为大家详细介绍了如何使用Python实现一个Excel批量样式修改器,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一... 目录前言功能特性核心功能界面特性系统要求安装说明使用指南基本操作流程高级功能技术实现核心技术栈关键函

使用Python批量将.ncm格式的音频文件转换为.mp3格式的实战详解

《使用Python批量将.ncm格式的音频文件转换为.mp3格式的实战详解》本文详细介绍了如何使用Python通过ncmdump工具批量将.ncm音频转换为.mp3的步骤,包括安装、配置ffmpeg环... 目录1. 前言2. 安装 ncmdump3. 实现 .ncm 转 .mp34. 执行过程5. 执行结

Python中 try / except / else / finally 异常处理方法详解

《Python中try/except/else/finally异常处理方法详解》:本文主要介绍Python中try/except/else/finally异常处理方法的相关资料,涵... 目录1. 基本结构2. 各部分的作用tryexceptelsefinally3. 执行流程总结4. 常见用法(1)多个e

SpringBoot 多环境开发实战(从配置、管理与控制)

《SpringBoot多环境开发实战(从配置、管理与控制)》本文详解SpringBoot多环境配置,涵盖单文件YAML、多文件模式、MavenProfile分组及激活策略,通过优先级控制灵活切换环境... 目录一、多环境开发基础(单文件 YAML 版)(一)配置原理与优势(二)实操示例二、多环境开发多文件版

Three.js构建一个 3D 商品展示空间完整实战项目

《Three.js构建一个3D商品展示空间完整实战项目》Three.js是一个强大的JavaScript库,专用于在Web浏览器中创建3D图形,:本文主要介绍Three.js构建一个3D商品展... 目录引言项目核心技术1. 项目架构与资源组织2. 多模型切换、交互热点绑定3. 移动端适配与帧率优化4. 可

SpringBoot 获取请求参数的常用注解及用法

《SpringBoot获取请求参数的常用注解及用法》SpringBoot通过@RequestParam、@PathVariable等注解支持从HTTP请求中获取参数,涵盖查询、路径、请求体、头、C... 目录SpringBoot 提供了多种注解来方便地从 HTTP 请求中获取参数以下是主要的注解及其用法:1

深度解析Java @Serial 注解及常见错误案例

《深度解析Java@Serial注解及常见错误案例》Java14引入@Serial注解,用于编译时校验序列化成员,替代传统方式解决运行时错误,适用于Serializable类的方法/字段,需注意签... 目录Java @Serial 注解深度解析1. 注解本质2. 核心作用(1) 主要用途(2) 适用位置3