最优雅的告别if-else方式(function、策略模式)

缘起

在开发过程中,经常会遇到业务逻辑扩展的情况,如果在编码之初没考虑到扩展性,我们很容易在代码中通过大量if-else实现将请求路由到不同的逻辑中。随着需求场景的无止境迭代,会出现大量if-else和冗余逻辑,一段200的代码可能有80行if-else,维护成本十分高昂,这时候就需要使用一定的设计模式来进行重构了。
在这里插入图片描述

这么说肯能有点抽象,举一个实际开发过程中遇到的例子;在开发内容生产加工链路中,期初内容类型比较单一,只有视频类型的内容以及对应的视频内容处理逻辑。随着业务的丰富,后续衍生出了图片、文本、短视频等多种类型的内容,每种类型内容都有其关联的特殊处理逻辑,简单粗暴的方案就是根据内容类型写一堆if-else,这么写的缺点在于后续维护性极差,每新增一种类型都需要在原if-else上进行修改,违反了设计模式中的开闭原则(对扩展开放,对修改关闭,程序进行扩展时,不应对老代码进行修改,实现一个可插拔的效果),在处理这类问题的优化思路,有一个使用非常频繁的设计模式——策略模式。

什么是策略模式?

策略模式(Strategy Pattern)属于对象的行为模式。其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。
其主要目的是通过定义相似的算法,替换if else 语句写法,并且可以随时相互替换。
在这里插入图片描述

策略模式一般包含三种角色:
• 环境角色(Context):持有一个策略类的引用,提供给客户端使用。
• 抽象策略角色(Strategy):通常由一个接口或抽象类实现,此角色给出所有的具体策略类所需的接口。
• 具体策略角色(ConcreteStrategy):包装了相关的算法或行为。
优点
• 扩展性好,通过新增具体策略扩展现有系统;
• 灵活性好,允许切换策略。
缺点
• 调用方必须知道所有的策略类才能进行调用。
• 策略实现多了后容易策略膨胀,增加了系统复杂度

怎么实现策略模式?

现在我们有三种内容类型,除了常规if-else或者switch-case之外,我们还可以将内容类型-处理策略的映射关系缓存至map中,为了进一步简化代码,我们利用spring bean生命周期的特性,在策略bean初始化完成后,自动加载至map,取策略时,直接通过map中取。
1.定义处理工厂类,通过实现ApplicationContextAware获取spring上下文,通过InitializingBean在bean加载完成后,注册入map


/**
 * handler工厂类
 *
 * @version 1.0.0
 * @since 2022/3/7
 */
@Component
public class ContentHandlerFactory implements InitializingBean, ApplicationContextAware {
    
    

    private ApplicationContext applicationContext;

    public static Map<String, Class<? extends BaseHandler>> handlerMap = Maps.newHashMap();

    @Override
    public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException {
    
    
        this.applicationContext = applicationContext;
    }

    @Override
    public void afterPropertiesSet() {
    
    
        Map<String, BaseHandler> beanMap = applicationContext.getBeansOfType(BaseHandler.class);
        beanMap.keySet().forEach(key -> {
    
    
            handlerMap.put(key, beanMap.get(key).getClass());
        });
    }

    /**
     * 根据handlerType获取指定handler
     * @param handlerType 类型:文件/内容
     * @return handler处理类
     */
    public BaseHandler getHandler(String handlerType) {
    
    
        if (!handlerMap.containsKey(handlerType)) {
    
    
            return null;
        }

        return applicationContext.getBean(handlerMap.get(handlerType));
    }
}

2.定义抽象接口

public interface BaseSecurityHandler {
    
    

    /**
     * handler处理逻辑
     * @param chainWorkerReq 生产链路上下文信息
     * @return 处理结果
     */
    ResultTO<Void> handler(ChainWorkerReq chainWorkerReq);

    /**
     * 获取处理器,如视频,图文
     *
     * @return handlerType 处理器类型
     */
    String getHandlerType();
}

3.定义视频内容处理策略

@Component
public class VideoHandler implements BaseHandler {
    
    

    @Override
    public ResultTO<Void> handler(ChainWorkerReq chainWorkerReq) {
    
    
        //这里是处理逻辑
        return null;
    }

    @Override
    public String getHandlerType() {
    
    
        return "video";
    }
}

4.根据内容类型获取策略并执行

@Resource
    static SecurityHandlerFactory securityHandlerFactory;

    public static void main(String[] args) {
    
    
        BaseHandler handler = null;

        if (视频内容) {
    
    
            //内容生产链路worker
            handler = securityHandlerFactory.getHandler("video");
        } else if (音频内容){
    
    
            handler = securityHandlerFactory.getHandler("audio");
        }
        
        //执行处理逻辑
        handler.handler()
    }

如何通过注解最优雅的实现策略模式?

在上述方案的基础上,进一步通过注解的方式实现自动注册,让代码进一步高大上
1.在spring中自定义注解
在这里插入图片描述

注解 Executor,在注解上加了Component 注解,这意味着Spring会把Executor注解修饰的类注册到容器中。

@Component
@Target({
    
    ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Executor {
    
    

    TypeEnum type();
}

2.扩展bean的生命周期,实现策略自动注册
简单介绍下Spring中bean的生命周期,主要分为四个阶段,依次是:bean的实例化(instantiation)->属性赋值->初始化(initialization)->销毁。Spring在bean的生命周期中提供了很多扩展点,例如Aware接口和BeanPostProcessor接口。
在这里插入图片描述
BeanPostProcessor接口定义如下:
在这里插入图片描述

从两个方法的名称可以看出,postProcessBeforeInitialization在bean初始化之前被调用,postProcessAfterInitialization 则在初始化后被调用。可以在我们自己的业务应用中实现该接口,达到定制bean的效果。
ExecutorProcessor重写了postProcessAfterInitialization方法,将使用了Executor注解的bean自动注册到策略缓存中。

@Component
public class ExecutorProcessor implements BeanPostProcessor {
    
    

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    
    
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    
    
        //反射获取bean是否使用了Executor
        Executor executor = AnnotationUtils.findAnnotation(AopUtils.getTargetClass(bean), Executor.class);
        if (executor == null) {
    
    
            return bean;
        }

        if (!(bean instanceof BaseHandler)) {
    
    
            //throw new BeansException();
        }

        registerStrategy(executor.type(), (BaseHandler)bean);
        return null;
    }
}

注册map管理class

@Component
public class StrategyExecutor {
    
    

    private static Map<TypeEnum, BaseHandler> strategyMap = new ConcurrentHashMap<>(16);

    /**
     * 注册任务处理器
     * @param taskTypeEnums 任务类型
     * @param taskStrategy  任务处理器
     */
    public static void registerStrategy(TypeEnum taskTypeEnums, BaseHandler taskStrategy) {
    
    
        strategyMap.put(taskTypeEnums, taskStrategy);
    }
}

参考文章:
spring生命周期:https://www.cnblogs.com/snidget/p/12633033.html?spm=ata.21736010.0.0.66895940932LPa

总结

简单的业务逻辑也可以通过设计模式和spring黑科技让代码更优雅,很多同学写代码容易陷入写逻辑对的坑,忽视了代码复用性,扩展性。
本文思路:思考 -> 抽象 -> 总结 -> 分享,分享是最好的老师,后续也会坚持更新我在大厂的所学所想,和大家一起进步。

猜你喜欢

转载自blog.csdn.net/weixin_43934939/article/details/124499859