是什么
为什么叫策略委派模式,而不是仅叫策略模式或仅叫委派模式?
网上很多人,拿着相同的代码去区分策略模式和委派模式,其实真的不用分的那么清,策略模式和委派模式本来就是一对天然的相辅相成的规律技巧。
索性,我根本不去纠结策略模式和委派模式的所谓区别,只要用起来符合我的需求即可。同族算法替换加上决策调度算法,那我就叫它策略委派模式行不行。
-
策略模式,只注重于同族算法封装及其可替代性,实际上只要一个接口加上一堆实现类即是策略模式。
-
所有策略都实现同一个接口或者继承同一个基类
-
策略和策略之间相互独立,没有任何影响
-
要求用户了解所有策略以及策略之间的区别,用户自行决定调用哪个策略实现类。
-
例如 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); } } }
-
-
委派模式,只注重于任务调度,只要有决策算法并根据决策算法调用不同的对象都能算是委派模式。
-
用户只管调用 委派者 然后等待处理结果,而 委派者 会根据自己的决策算法,调用不同的 受委派者 帮自己执行任务。
-
委派者 和 受委派者 之间严格不要求实现同一个接口或继承同一个基类,只要能够让后者被前者调用即可。
-
委派者 和 受委派者 之间一定是组合关系
-
不需要用户了解委派过程,不需要了解有什么类接受了委派,只需要等结果即可。
-
例如: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()); }
-
为什么
为什么要这么实现,有什么好处?
- 可以取代冗长的
if-else
和switch-case
语法 - 可以增加代码可扩展性和易维护性,在项目持续迭代中,只需要增加策略接口实例即可,复用代码可以向上抽象
- 一定程度可以增加代码可阅读性,细分业务场景
怎么做
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));