实现模板:Spring下的策略委派模式

是什么

为什么叫策略委派模式,而不是仅叫策略模式或仅叫委派模式?

网上很多人,拿着相同的代码去区分策略模式和委派模式,其实真的不用分的那么清,策略模式和委派模式本来就是一对天然的相辅相成的规律技巧。

索性,我根本不去纠结策略模式和委派模式的所谓区别,只要用起来符合我的需求即可。同族算法替换加上决策调度算法,那我就叫它策略委派模式行不行。

  1. 策略模式,只注重于同族算法封装及其可替代性,实际上只要一个接口加上一堆实现类即是策略模式。

    1. 所有策略都实现同一个接口或者继承同一个基类

    2. 策略和策略之间相互独立,没有任何影响

    3. 要求用户了解所有策略以及策略之间的区别,用户自行决定调用哪个策略实现类。

    4. 例如 Spring 的 Resource 接口及其众多实现类就属于典型的策略模式,不同的路径格式,调用不同的策略实现类。

      org.springframework.core.io.DefaultResourceLoader#getResource

      @Override
      public Resource getResource(String location) {
         Assert.notNull(location, "Location must not be null");
      
         for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
            Resource resource = protocolResolver.resolve(location, this);
            if (resource != null) {
               return resource;
            }
         }
      
         if (location.startsWith("/")) {
            return getResourceByPath(location);
         }
         else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
            return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
         }
         else {
            try {
               // Try to parse the location as a URL...
               URL url = new URL(location);
               return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
            }
            catch (MalformedURLException ex) {
               // No URL -> resolve as resource path.
               return getResourceByPath(location);
            }
         }
      }
      
  2. 委派模式,只注重于任务调度,只要有决策算法并根据决策算法调用不同的对象都能算是委派模式。

    1. 用户只管调用 委派者 然后等待处理结果,而 委派者 会根据自己的决策算法,调用不同的 受委派者 帮自己执行任务。

    2. 委派者受委派者 之间严格不要求实现同一个接口或继承同一个基类,只要能够让后者被前者调用即可。

    3. 委派者受委派者 之间一定是组合关系

    4. 不需要用户了解委派过程,不需要了解有什么类接受了委派,只需要等结果即可。

    5. 例如:Spring 的 DefaultResourceLoader 就有委派模式,调用其 getResource() 方法,会根据不同的路径格式进行决策,调用不同的 Resource 实现类。

      @Test 
      public void testResourceLoad() { 
          ResourceLoader loader = new DefaultResourceLoader(); 
          Resource resource = loader.getResource("classpath:cn/javass/spring/chapter4/test1.txt"); 
          //验证返回的是ClassPathResource 
          Assert.assertEquals(ClassPathResource.class, resource.getClass()); 
          Resource resource2 = loader.getResource("file:cn/javass/spring/chapter4/test1.txt"); 
          //验证返回的是UrlResource
          Assert.assertEquals(UrlResource.class, resource2.getClass()); 
      } 
      

为什么

为什么要这么实现,有什么好处?

  1. 可以取代冗长的 if-elseswitch-case 语法
  2. 可以增加代码可扩展性和易维护性,在项目持续迭代中,只需要增加策略接口实例即可,复用代码可以向上抽象
  3. 一定程度可以增加代码可阅读性,细分业务场景

怎么做

1. 策略接口

/**
 * 策略接口
 *
 * @author Kitman
 * @version 2.0
 * @date 2022/12/15 20:50
 */
public interface StrategyExecutor extends PriorityOrdered{

    /**
     * 根据业务环境上下文匹配当前策略
     *
     * @param strategyContext 业务环境上下文
     * @return 是否匹配
     */
    boolean isSupport(StrategyContext strategyContext);

    /**
     * 执行
     *
     * @param startParam 开启参数
     */
    void execute(StrategyContext startParam);
}

定义一个基本环境类,统一通过入参对接环境数据


/**
 * 业务环境上下文,存储本会话或本线程内的业务数据
 *
 * @author Kitman
 * @version 2.0
 * @date 2022/12/15 22:05
 */
@Data
public class StrategyContext {

    // 通用参数...省略
}

2. 抽象策略基类

如果直接根据策略接口收集所有实现类的话,范围太大,有时候需要一些更恰当的分类,例如可能会需要一个默认的空策略来增加代码的健壮性。

/**
 * 抽象策略基类
 * 继承本类并且配合 {@link org.springframework.stereotype.Component} 注解可以被自动收集
 *
 * @author Kitman
 * @version 2.0
 * @date 2022/12/15 21:06
 */
@Slf4j
public abstract class AbstractStrategyExecutor implements StrategyExecutor{

    /**
     * 依赖注入需要子类提供
     */
    protected final ObjectMapper objectMapper;

    protected AbstractStrategyExecutor(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Override
    public boolean isSupport(StrategyContext strategyContext) {
        // 通用策略匹配算法
        return true;
    }
}

3. 不被收集的默认空策略

可能会需要一个不被策略容器收集的,默认的空策略来增加代码的健壮性。

/**
 * 空策略,什么都不做,避免空指针异常
 *
 * @author Kitman
 * @version 2.0
 * @date 2022/12/15 21:54
 */
public class NoneStrategyExecutor implements StrategyExecutor {

    @Override
    public boolean isSupport(StrategyContext strategyContext) {
        return false;
    }

    @Override
    public void execute(StrategyContext startParam) {
        log.info("Didn't execute any step, didn't do anything");
    }
}

4. 被自动收集的策略实例

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * 发送短信通知策略
 *
 * @author Kitman
 * @version 2.0
 * @date 2022/12/15 21:02
 */
@Component
@Slf4j
public class SmsStrategyExecutor extends AbstractStrategyExecutor {

    private static final StrategyTypeEnum STRATEGY_TYPE = "发送短信通知策略";

    @Autowired
    public SmsStrategyExecutor(ObjectMapper objectMapper) {super(objectMapper);}

    @Override
    public boolean isSupport(StrategyContext strategyContext) {
        return super.isSupport(strategyContext) && STRATEGY_TYPE.equals(strategyContext.getType());
    }

    @Override
    public void execute(StrategyContext startParam) {
        // 具体策略执行程序...省略
    }
}

5. 策略容器和调度管理器

import cn.hutool.core.collection.CollUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 策略容器和委派调度管理器
 * 
 * @author Kitman
 * @version 2.0
 * @date 2022/12/15 21:16
 */
@Component
@Slf4j
public class StrategyExecutorManager implements InitializingBean, ApplicationContextAware {

    private transient ApplicationContext applicationContext;
    private List<StrategyExecutor> strategyExecutors;
    private final NoneStrategyExecutor nonestrategyExecutor;

    public StrategyExecutorManager() {this.nonestrategyExecutor = new NoneStrategyExecutor();}

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

    @Override
    public void afterPropertiesSet() throws Exception {
        // 收集所有 strategyExecutor
        final Map<String, AbstractStrategyExecutor> beanMap = applicationContext.getBeansOfType(AbstractStrategyExecutor.class);
        strategyExecutors = new ArrayList<>(beanMap.values());
        log.info("收集 StrategyExecutor 实例,共计:{}", strategyExecutors.size());
    }

    public void execute(StrategyContext param) {
        Assert.notNull(param, "参数不能为空:param");
        if (CollUtil.isNotEmpty(strategyExecutors)) {
            strategyExecutors.stream()
                // 匹配决策
                .filter(starter -> starter.isSupport(param))
                // 排序
                .min(Comparator.comparingInt(Ordered::getOrder))
                .orElse(nonestrategyExecutor)
                // 执行
                .execute(param);
        } else {
            nonestrategyExecutor.execute(null);
        }
    }
}

如果策略不要求返回值,且需要委派给多个策略的话,可以将匹配决策这么写:

strategyExecutors.stream()
   // 匹配决策
   .filter(starter -> starter.isSupport(param))
   // 排序
   .sorted(Comparator.comparingInt(Ordered::getOrder))
   // 执行
   .forEach(starter -> starter.execute(param));
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值