如何优雅的用策略模式,取代臃肿的 if-else 嵌套,看这篇就够了
经常听同事抱怨,订单来源又加了一种,代码又要加一层if-else判断,光判断订单来源的if-else就好几百行代码,代码我都不想看了,相信很多同行都有过这样的感受!
Java的二十几种设计模式背的滚瓜烂熟,为什么这个时候不想着尝试用一下?说不定能轻松的解决掉哦
先说一下具体的需求:
公司推广入口很多,每一个下单来源在下单时都做特殊的逻辑处理,可能每两天就会加一个来源
一、传统的实现方式
那么按照传统的实现方式代码就是如下:
public class OrderServiceImpl implements IOrderService {
@Override
public String handle(OrderDTO dto) {
String type = dto.getType();
if ("1".equals(type)) {
return "处理普通订单";
} else if ("2".equals(type)) {
return "处理团购订单";
} else if ("3".equals(type)) {
return "处理促销订单";
}
return null;
}
}
为什么非得写的这么臃肿?很多同事会说:“哎呀,没办法呀,业务催的紧,这样开发效率快省事”。的确是句大实话,很多时候业务方确实像催命鬼一样的让你赶工期,想快速实现功能,这样写是最好的选择。
上边的代码看似还算清晰,可如果我告诉你公司订单来源有上百种,你想象一下那种臃肿的if-else,去翻代码时是什么感受?
二、策略模式的实现方式
策略模式是oop中最著名的设计模式之一,是对方法行为的抽象,可以归类为行为设计模式,也是oop中interface经典的应用。其特点简单又实用,是我最喜欢的模式之一。
策略模式定义了一个拥有共同行为的算法族,每个算法都被封装起来,可以互相替换,独立于客户端而变化。
不少人说:Java的设计模式背了很多,可日常还不就是写if-else的业务,根本就不用到。其实不是用不到是没有用到合适的位置!
策略模式的使用场景:
- 针对同一问题的多种处理方式,仅仅是具体行为有差别时;
- 需要安全地封装多种同一类型的操作时;
- 同一抽象类有多个子类,而客户端需要使用if-else 或者 switch-case 来选择具体子类时。
这个是用策略模式修改后代码:
@Component
@OrderHandlerType(16)
public class DispatchModeProcessor extends AbstractHandler{
@Autowired
private OrderStencilledService orderStencilledService;
@Override
public void handle(OrderBO orderBO) {
/**
* 订单完结广播通知(1 - 支付完成)
*/
orderStencilledService.dispatchModeFanout(orderBO);
/**
* SCMS 出库单
*/
orderStencilledService.createScmsDeliveryOrder(orderBO.getPayOrderInfoBO().getLocalOrderNo());
}
}
每个订单来源都有自己单独的逻辑实现类,而每次需要添加订单来源,直接新建实现类,修改@OrderHandlerType(16)的数值即可,再也不用去翻那几百行的if-lese,一劳永逸!
具体的实现过程:
1、定义一个标识订单来源的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface OrderHandlerType {
int value() default 0;
}
2、抽象出来一个具体的业务处理器
public abstract class AbstractHandler {
abstract public void handle(OrderBO orderBO);
}
3、项目启动扫描 handler 入口
@Component
@SuppressWarnings({"unused","rawtypes"})
public class HandlerProcessor implements BeanFactoryPostProcessor {
private String basePackage = "com.ecej.order.pipeline.processor";
public static final Logger log = LoggerFactory.getLogger(HandlerProcessor.class);
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
Map<Integer,Class> map = new HashMap<Integer,Class>();
ClassScaner.scan(basePackage, OrderHandlerType.class).forEach(x ->{
int type = x.getAnnotation(OrderHandlerType.class).value();
map.put(type,x);
});
beanFactory.registerSingleton(OrderHandlerType.class.getName(), map);
log.info("处理器初始化{}", JSONObject.toJSONString(beanFactory.getBean(OrderHandlerType.class.getName())));
}
}
4、扫描需要用到的工具类
public class ClassScaner {
private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
private final List<TypeFilter> includeFilters = new ArrayList<TypeFilter>();
private final List<TypeFilter> excludeFilters = new ArrayList<TypeFilter>();
private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
/**
* 添加包含的Fiter
* @param includeFilter
*/
public void addIncludeFilter(TypeFilter includeFilter) {
this.includeFilters.add(includeFilter);
}
/**
* 添加排除的Fiter
* @param includeFilter
*/
public void addExcludeFilter(TypeFilter excludeFilter) {
this.excludeFilters.add(excludeFilter);
}
/**
* 扫描指定的包,获取包下所有的Class
* @param basePackage 包名
* @param targetTypes 需要指定的目标类型,可以是pojo,可以是注解
* @return Set<Class<?>>
*/
public static Set<Class<?>> scan(String basePackage,
Class<?>... targetTypes) {
ClassScaner cs = new ClassScaner();
for (Class<?> targetType : targetTypes){
if(TypeUtils.isAssignable(Annotation.class, targetType)){
cs.addIncludeFilter(new AnnotationTypeFilter((Class<? extends Annotation>) targetType));
}else{
cs.addIncludeFilter(new AssignableTypeFilter(targetType));
}
}
return cs.doScan(basePackage);
}
/**
* 扫描指定的包,获取包下所有的Class
* @param basePackages 包名,多个
* @param targetTypes 需要指定的目标类型,可以是pojo,可以是注解
* @return Set<Class<?>>
*/
public static Set<Class<?>> scan(String[] basePackages,
Class<?>... targetTypes) {
ClassScaner cs = new ClassScaner();
for (Class<?> targetType : targetTypes){
if(TypeUtils.isAssignable(Annotation.class, targetType)){
cs.addIncludeFilter(new AnnotationTypeFilter((Class<? extends Annotation>) targetType));
}else{
cs.addIncludeFilter(new AssignableTypeFilter(targetType));
}
}
Set<Class<?>> classes = new HashSet<Class<?>>();
for (String s : basePackages){
classes.addAll(cs.doScan(s));
}
return classes;
}
/**
* 扫描指定的包,获取包下所有的Class
* @param basePackages 包名
* @return Set<Class<?>>
*/
public Set<Class<?>> doScan(String [] basePackages) {
Set<Class<?>> classes = new HashSet<Class<?>>();
for (String basePackage :basePackages) {
classes.addAll(doScan(basePackage));
}
return classes;
}
/**
* 扫描指定的包,获取包下所有的Class
* @param basePackages 包名
* @return Set<Class<?>>
*/
public Set<Class<?>> doScan(String basePackage) {
Set<Class<?>> classes = new HashSet<Class<?>>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ ClassUtils.convertClassNameToResourcePath(
SystemPropertyUtils.resolvePlaceholders(basePackage))+"/**/*.class";
Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
for (int i = 0; i < resources.length; i++) {
Resource resource = resources[i];
if (resource.isReadable()) {
MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
if ((includeFilters.size() == 0 && excludeFilters.size() == 0)|| matches(metadataReader)) {
try {
classes.add(Class.forName(metadataReader.getClassMetadata().getClassName()));
} catch (ClassNotFoundException ignore) {}
}
}
}
} catch (IOException ex) {
throw new RuntimeException("I/O failure during classpath scanning", ex);
}
return classes;
}
/**
* 处理 excludeFilters和includeFilters
* @param metadataReader
* @return boolean
* @throws IOException
*/
private boolean matches(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, this.metadataReaderFactory)) {
return true;
}
}
return false;
}
}
5、 根据类型实例化抽象类
@Component
public class HandlerContext {
@Autowired
private ApplicationContext beanFactory;
public AbstractHandler getInstance(Integer type){
Map<Integer,Class> map = (Map<Integer, Class>) beanFactory.getBean(OrderHandlerType.class.getName());
return (AbstractHandler)beanFactory.getBean(map.get(type));
}
}
6、调用入口,我这里是接的MQ消息,会批量的处理多个订单来源
@Component
@RabbitListener(queues = "OrderPipelineQueue")
public class PipelineSubscribe{
private final Logger LOGGER = LoggerFactory.getLogger(PipelineSubscribe.class);
@Autowired
private HandlerContext HandlerContext;
@Autowired
private OrderValidateService orderValidateService;
@RabbitHandler
public void subscribeMessage(MessageBean bean){
OrderBO orderBO = JSONObject.parseObject(bean.getOrderBO(), OrderBO.class);
if(null != orderBO &&CollectionUtils.isNotEmpty(bean.getType()))
{
for(int value:bean.getType())
{
AbstractHandler handler = HandlerContext.getInstance(value);
handler.handle(orderBO);
}
}
}
}
接收实体 MessageBean 类代码
public class MessageBean implements Serializable {
private static final long serialVersionUID = 5454831432308782668L;
private String cachKey;
private List<Integer> type;
private String orderBO;
public MessageBean(List<Integer> type, String orderBO) {
this.type = type;
this.orderBO = orderBO;
}
}
策略模式的优缺点
优点
- 易于扩展,增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合开放封闭原则
- 避免使用多重条件选择语句,充分体现面向对象设计思想 策略类之间可以自由切换,由于策略类都实现同一个接口,所以使它们之间可以自由切换
- 每个策略类使用一个策略类,符合单一职责原则 客户端与策略算法解耦,两者都依赖于抽象策略接口,符合依赖反转原则
- 客户端不需要知道都有哪些策略类,符合最小知识原则
缺点
- 策略模式,当策略算法太多时,会造成很多的策略类
- 客户端不知道有哪些策略类,不能决定使用哪个策略类,这点可以通过封装common公共包解决,也可以考虑使
IOC容器和依赖注入的方式来解决
订单来源策略类的一部分,确实策略类很多

总结:
if else多层嵌套和策略模式有各自的优缺点
- 优点:想快速迭代功能,逻辑嵌套少,且不会持续增加,if else更好些;缺点: 代码臃肿不便于维护
- 优点:多同一抽象类有多个子类,需要使用if-else 或者 switch-case 来选择具体子类时,建议选策略模式;缺点:策略类文件太多
两种实现方式各有利弊,选择的时候还是要依据具体业务,还是那句话设计模式不是为了用而用,要有一个合适应用场景。
如何优雅的用策略模式,取代臃肿的 if-else 嵌套,看这篇就够了的更多相关文章
- 大型Java进阶专题(七) 设计模式之委派模式与策略模式
前言 今天开始我们专题的第七课了.本章节将介绍:你写的代码中是否觉得很臃肿,程序中有大量的if...else,想优化代码,精简程序逻辑,提升代码的可读性,这章节将介绍如何通过委派模式.策略模式让你 ...
- [Head First设计模式]策略模式
系列文章 [Head First设计模式]山西面馆中的设计模式——装饰者模式 [Head First设计模式]山西面馆中的设计模式——观察者模式 [Head First设计模式]山西面馆中的设计模式— ...
- Head First设计模式之策略模式(Strategy Pattern)
前言: 刚刚开始学习设计模式,之前也接触过一些,但是从来都没有系统的学过,这次打算好好的学习一下.这里就当是对学习过程的一个记录.整理,以便可以在以后不时的温故知新. 这一节采用一个鸭子的示例,层层推 ...
- javascript 设计模式-----策略模式
在<javascript设计模式>中,作者并没有向我们介绍策略模式,然而它却是一种在开发中十分常见的设计模式.最常见的就是当我们遇到一个复杂的表单验证的时候,常常需要编写一大段的if和el ...
- Java设计模式6:策略模式
策略模式 策略模式的用意是针对一组算法,将每一个算法封装到具有共同接口的独立类中,从而使得它们可以相互替换.策略模式使得算法可以在不影响到客户端的情况下发生变化. 策略模式的结构 策略模式是对算法的包 ...
- javascript设计模式实践之策略模式--输入验证
策略模式中的策略就是一种算法或者业务规则,将这些策略作为函数进行封装,并向外提供统一的调用执行. 先定义一个简单的输入表单: <!DOCTYPE html> <html> &l ...
- C++ 之 策略模式
1 会飞的鸭子 Duck 基类,包含两个成员函数 swim() 和 display():派生类 MallardDuck,RedheadDuck 和 RubberDuck,各自重写 display() ...
- 设计模式 ( 十八 ) 策略模式Strategy(对象行为型)
设计模式 ( 十八 ) 策略模式Strategy(对象行为型) 1.概述 在软件开发中也经常遇到类似的情况,实现某一个功能有多种算法或者策略,我们能够依据环境或者条件的不同选择不同的算法或者策略来完毕 ...
- Provider Pattern提供者模式和策略模式
http://www.codeproject.com/Articles/18222/Provider-Pattern Introduction Provider pattern is one of t ...
随机推荐
- css图片填充的几种方式
当图片比例不固定时,想要让图片自适应,一般都会用background-size:cover/contain,但是这个只适用于背景图. img有个属性object-fit 属性值:object-fit: ...
- POJ 2778 DNA Sequence (ac自动机+矩阵快速幂)
DNA Sequence Description It's well known that DNA Sequence is a sequence only contains A, C, T and G ...
- html2canvas生成图片报错处理办法
详见大佬博客链接: link.(https://www.jianshu.com/p/22bd5b98e38a) 需要注意的是要生成的网页中带的网络图片地址(如放在阿里云服务器图库的图片)经常有跨域报错 ...
- H3C重启设备
- C# 文件在数据库 的 存取
... /// <summary> /// 获取数据库Image字段数据,保存到本地 /// </summary> /// <param name="sende ...
- rabbitmq template发送的消息中,Date类型字段比当前时间晚了8小时
前言 前一阵开发过程遇到的问题,用的rabbitmq template发送消息,消息body里的时间是比当前时间少了8小时的,这种一看就是时区问题了. 就说说为什么出现吧. 之前的配置是这样的: @B ...
- 2020年. NET Core面试题
第1题,什么是ASP net core? 首先ASP net core不是 asp net的升级版本.它遵循了dot net的标准架构, 可以运行于多个操作系统上.它更快,更容易配置,更加模块化,可扩 ...
- 【时区问题】SpringBoot+mybatis查询mysql的datetime类型数据时间差14小时
[时区问题]MyBatis查询MySQL的datetime类型数据时间差14小时 故障解决方式 与数据库连接时,定义时区,避免mybatis框架从mysql获取时区.在连接上加上 serverTime ...
- 洛谷$P3756\ [CQOI2017]$老$C$的方块 网络流
正解:网络流 解题报告: 传送门$QwQ$ 看到不能出现给定的讨厌的图形,简单来说就,特殊边两侧的方格不能同时再连方格. 所以如果出现,就相当于是四种方案?就分别炸四个格子. 然后冷静分析一波之后发现 ...
- Windows Live Writer 语法高亮
1.WindowsLiveWriter.CNBlogs.CodeHighlighter.rar 这个插件生成的高亮代码与网页上的一模一样,插入后即可立即显示效果,不过貌似它必须联网才能实时显示效果,因 ...