zoukankan      html  css  js  c++  java
  • 如何优雅的用策略模式,取代臃肿的 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 来选择具体子类时,建议选策略模式;缺点:策略类文件太多

    两种实现方式各有利弊,选择的时候还是要依据具体业务,还是那句话设计模式不是为了用而用,要有一个合适应用场景。


    关注微信公众号:【程序员内点事】,免费获取2000学习资料,内含精选面试题,SSM、Spring全家桶、微服务、MySQL、MyCat、集群、分布式、中间件、Linux、网络、多线程,Jenkins、Nexus、Docker、ELK等等免费学习视频,持续更新!

  • 相关阅读:
    Linux perf命令详解及常用参数解析
    pidstat 命令(Linux 进程使用资源情况采样)
    Katalon Studio入门学习之三种获取元素方式
    用Spring和c3p0工具简单的实现增删改查
    Spring AOP(面向切面示例)
    Spring属性注入、构造方法注入、工厂注入以及注入参数(转)
    PowerDesigner最基础的使用方法入门学习(转)
    Struts文件上传
    struts转换器
    Struts拦截器
  • 原文地址:https://www.cnblogs.com/chengxy-nds/p/12205104.html
Copyright © 2011-2022 走看看