当前位置:首页 > 公众号精选 > 架构师社区
[导读]最近公司貌似融到资了!开始发了疯似的找渠道推广,现在终于明白为啥前一段大肆的招人了,原来是在下一盘大棋,对员工总的来看是个好事,或许是时候该跟boss提一提涨工资的话题了。 不过,涨工资还没下文,随之而来的却是一车一车的需求,每天都有新渠道接入

<section data-website="https://www.mdnice.com" data-mpa-powered-by="yiban.io">

被迫重构代码,这次我干掉了 if-else

最近公司貌似融到资了!开始发了疯似的找渠道推广,现在终于明白为啥前一段大肆的招人了,原来是在下一盘大棋,对员工总的来看是个好事,或许是时候该跟boss提一提涨工资的话题了。

不过,涨工资还没下文,随之而来的却是一车一车的需求,每天都有新渠道接入,而且每个渠道都要提供个性化支持,开发量陡增。最近都没什么时间更文,准点下班都成奢望了!

被迫重构代码,这次我干掉了 if-else

由于推广渠道的激增,而每一个下单来源在下单时都做特殊的逻辑处理,可能每两天就会加一个来源,已经把之前的下单逻辑改的面目全。出于长远的考虑,我决定对现有的逻辑进行重构,毕竟长痛不如短痛。

传统的实现方式

我们看下边的伪代码,大致就是重构前下单逻辑的代码,由于来源比较少,简单的做if-else逻辑判断足以满足需求。

现在每种订单来源的处理逻辑都有几百行代码,看着已经比较臃肿,可我愣是迟迟没动手重构,一方面业务方总像催命鬼一样的让你赶工期,想快速实现需求,这样写是最快;另一方面是不敢动,面对古董级代码,还是想求个安稳。

但这次来源一下子增加几十个,再用这种方式做已经无法维护了,想象一下那种臃肿的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;
    }
}

策略模式的实现方式

思来想去基于当前业务场景重构,还是用策略模式比较合适,它是oop中比较著名的设计模式之一,对方法行为的抽象。

策略模式定义了一个拥有共同行为的算法族,每个算法都被封装起来,可以互相替换,独立于客户端而变化。

一、策略模式的使用场景:

  • 针对同一问题的多种处理方式,仅仅是具体行为有差别时;
  • 需要安全地封装多种同一类型的操作时;
  • 同一抽象类有多个子类,而客户端需要使用 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、定义注解

定义一个标识订单来源的注解@OrderHandlerType

@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;
    }
}

以上设计模式方式看着略显复杂,很些小伙伴提出质疑:“你为了个if-else,弄的如此的麻烦,又是自定义注解,又弄这么多类不麻烦吗?”  还有一些小伙伴纠结于性能问题,策略模式的性能可能确实不如if-else

但我觉得吧增加一点复杂度、牺牲一丢丢性能,换代码的整洁和可维护性还是值得的。不过,一个人一个想法,怎么选还是看具体业务场景吧!

被迫重构代码,这次我干掉了 if-else

策略模式的优缺点

优点

  • 易于扩展,增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合开放封闭原则
  • 避免使用多重条件选择语句,充分体现面向对象设计思想 策略类之间可以自由切换,由于策略类都实现同一个接口,所以使它们之间可以自由切换
  • 每个策略类使用一个策略类,符合单一职责原则 客户端与策略算法解耦,两者都依赖于抽象策略接口,符合依赖反转原则
  • 客户端不需要知道都有哪些策略类,符合最小知识原则

缺点

  • 策略模式,当策略算法太多时,会造成很多的策略类
  • 客户端不知道有哪些策略类,不能决定使用哪个策略类,这点可以通过封装common公共包解决,也可以考虑使 IOC容器依赖注入的方式来解决。

以下是订单来源策略类的一部分,不得不说策略类确实比较多。被迫重构代码,这次我干掉了 if-else

总结

凡事都有他的两面性,if-else多层嵌套和也都有其各自的优缺点:

  • if-else的优点就是简单,想快速迭代功能,逻辑嵌套少且不会持续增加,if-else更好些,缺点也是显而易见,代码臃肿繁琐不便于维护。

  • 策略模式 将各个场景的逻辑剥离出来维护,同一抽象类有多个子类,需要使用if-else 或者 switch-case 来选择具体子类时,建议选策略模式,他的缺点就是会产生比较多的策略类文件。

两种实现方式各有利弊,如何选择还是要依据具体业务场景,还是那句话设计模式不是为了用而用,一定要用在最合适的位置。

闲聊

平常和粉丝私下聊天,好多人对于学设计模式的感受:设计模式背了一大堆,可平常开发还不是成天写if-else业务逻辑,根本就用不到。

学设计模式也不是用不到,只是有时候没有合适它的场景而已,像我们今天说的这种业务场景,用设计模式就可以完美的解决嘛。

学了N多技术可工作用不到是一种很常见的事情,一个稳定的项目使用一种技术会有诸多考量的,新技术会不会提升系统复杂度?它有哪些性能瓶颈?这些都必须考虑到,毕竟项目稳定才是最重要,谁也不敢轻易冒险尝试。

而我们学习技术可不仅为了眼下项目中是否会用到,是要做一个技术积累,做长远打算,人往高处走,没点能力可不行。

特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:

被迫重构代码,这次我干掉了 if-else

长按订阅更多精彩▼

被迫重构代码,这次我干掉了 if-else

如有收获,点个在看,诚挚感谢

section>

免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

9月2日消息,不造车的华为或将催生出更大的独角兽公司,随着阿维塔和赛力斯的入局,华为引望愈发显得引人瞩目。

关键字: 阿维塔 塞力斯 华为

加利福尼亚州圣克拉拉县2024年8月30日 /美通社/ -- 数字化转型技术解决方案公司Trianz今天宣布,该公司与Amazon Web Services (AWS)签订了...

关键字: AWS AN BSP 数字化

伦敦2024年8月29日 /美通社/ -- 英国汽车技术公司SODA.Auto推出其旗舰产品SODA V,这是全球首款涵盖汽车工程师从创意到认证的所有需求的工具,可用于创建软件定义汽车。 SODA V工具的开发耗时1.5...

关键字: 汽车 人工智能 智能驱动 BSP

北京2024年8月28日 /美通社/ -- 越来越多用户希望企业业务能7×24不间断运行,同时企业却面临越来越多业务中断的风险,如企业系统复杂性的增加,频繁的功能更新和发布等。如何确保业务连续性,提升韧性,成...

关键字: 亚马逊 解密 控制平面 BSP

8月30日消息,据媒体报道,腾讯和网易近期正在缩减他们对日本游戏市场的投资。

关键字: 腾讯 编码器 CPU

8月28日消息,今天上午,2024中国国际大数据产业博览会开幕式在贵阳举行,华为董事、质量流程IT总裁陶景文发表了演讲。

关键字: 华为 12nm EDA 半导体

8月28日消息,在2024中国国际大数据产业博览会上,华为常务董事、华为云CEO张平安发表演讲称,数字世界的话语权最终是由生态的繁荣决定的。

关键字: 华为 12nm 手机 卫星通信

要点: 有效应对环境变化,经营业绩稳中有升 落实提质增效举措,毛利润率延续升势 战略布局成效显著,战新业务引领增长 以科技创新为引领,提升企业核心竞争力 坚持高质量发展策略,塑强核心竞争优势...

关键字: 通信 BSP 电信运营商 数字经济

北京2024年8月27日 /美通社/ -- 8月21日,由中央广播电视总台与中国电影电视技术学会联合牵头组建的NVI技术创新联盟在BIRTV2024超高清全产业链发展研讨会上宣布正式成立。 活动现场 NVI技术创新联...

关键字: VI 传输协议 音频 BSP

北京2024年8月27日 /美通社/ -- 在8月23日举办的2024年长三角生态绿色一体化发展示范区联合招商会上,软通动力信息技术(集团)股份有限公司(以下简称"软通动力")与长三角投资(上海)有限...

关键字: BSP 信息技术
关闭
关闭