如何优雅的用策略模式,取代臃肿的 if-else 嵌套,看这篇就够了
一、传统的实现方式
先说一下具体的需求:公司推广入口很多,每一个下单来源在下单时都做特殊的逻辑处理,可能每两天就会加一个来源。
那么按照传统的实现方式代码就是如下:
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的业务,根本就不用到。其实不是用不到是没有用到合适的位置!
1、策略模式的使用场景:
- 针对同一问题的多种处理方式,仅仅是具体行为有差别时;
- 需要安全地封装多种同一类型的操作时;
- 同一抽象类有多个子类,而客户端需要使用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,一劳永逸!
2、具体的实现过程:
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
更好些,缺点也是显而易见,代码臃肿繁琐不便于维护。
策略模式
将各个场景的逻辑剥离出来维护,同一抽象类有多个子类,需要使用if-else
或者 switch-case
来选择具体子类时,建议选策略模式,他的缺点就是会产生比较多的策略类文件。
两种实现方式各有利弊,如何选择还是要依据具体业务场景,还是那句话设计模式不是为了用而用,一定要用在最合适的位置。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
如何快速的学习一门新的编程语言?
工作中,经常有工作两年左右的同事,用手挠挠发量本来就不多的脑袋,问我:“我如何快速上手一门新语言呢?” 程序世界变化非常快,新的框架、语言,新的词汇层出不穷,那作为有追求的技术人员,如何快速地跟上这些变化,使自己保持竞争力,而不落伍呢? 难学的第二门语言 上学的时候,一位老师在与新手开发人员分享经验的时候曾说:“最难学的编程语言是第二门语言”。 这是因为,在你第一次学习编程时,就已经对编程有了一些先入为主的想法。你会在语法上做很多的联想和假设。因此,在学习第二门语言时,你必须首先忘掉这些假设。在你学习第二门甚至是第三门语言时,一定要牢记这一点。这跟我们之前提到的:有趣的“第二个系统”描述的是同样的道理。 语言设计的目的 每种语言的每个方面都可以归结为真与假。为什么?电的工作方式就是这样:要么有电,要么没电。内存以0和1的形式存储值,这个最基本的单位叫做比特,而比特要么为真要么为假。 8比特等于一个字节,足以表示ASCII表中的任何字符。这些比特以特定的顺序翻转,以提供字符的十进制表示形式。计算机知道如何将这种表示形式转换为字母。 基本的二进制表示形式,表示了单词“Hello”。 理解...
- 下一篇
2021年5月国产数据库排行榜:十强榜单固若金汤
本月共有127个数据库参与排名,TiDB、OceanBase 和 PolarDB继续保持着上个月的“T-O-P”阵容,GaussDB上升至第六位与TDSQL互换顺序,TOP10固若金汤。 2021年5月国产数据库排行榜新鲜出炉,本月TOP10榜单固若金汤,同时排名较往期变化较小,其中过半产品(6款)得分呈负增长,TiDB、OceanBase 和 PolarDB继续保持着上个月的“T-O-P”阵容,GaussDB上升至第六位与TDSQL互换顺序,其他8款产品均无排名变化。 状元TiDB流行度得分较上月虽减少8.18分,仍领先第二位132.25分。榜眼OceanBase和探花PolarDB得分依次上涨7.6%和7.3%,两产品得分连续两月上涨。 本月共有127个数据库参与排名,数量与上期一致。 图1 2021年5月国产数据库流行度排行 (图片来源于墨天轮排行榜) 导读目录 5月一甲榜单(TOP1~3) TOP 1: TiDB,PingCAP获GDCC表彰,5.0 GA发版 TOP 2: OceanBase与央行数字研究所签合作协议,问答区上线巩固生态 TOP 3: PolarDB 5月二...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS关闭SELinux安全模块
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS6,CentOS7官方镜像安装Oracle11G
- Docker安装Oracle12C,快速搭建Oracle学习环境
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作