java设计模式一共有23种,其中主要分为三大类:
1:创建型模式
工厂方法模式、抽象工厂模式、单例模式、创建者模式、原型模式。
2:结构型模式
适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
3:行为模式
策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模状态模式、访问者模式、中介者模式、解释器模式。
今天我们主要讲解行为模式中的策略模式:
白话讲策略模式
什么是策略模式:说白点,就是我们可以使用很多种方法实现我们想要的效果,那么每一个方法就可以理解为一个策略策略。
适用场景:多种方案选择,提供多种方案进行选择,每种方案都是一个策略,最终目的一样,具体实现不一样。
真实案例:
1、需求分析:聚合支付的时候,一般有支付宝支付、微信支付、银联支付,那么这个时候就可以选择策略模式进行设计,避免多重选择。每一个支付平台都是一种策略的实现,其中我们的目的是为了支付,根据用户的选择完成扣款等一些列操作。虽然我们的目的一样,但是具体的每一个平台的实现方式不一样。那么每个支付平台就是一个策略。
2、需求分析:某品牌全国各地存在多家经销商,用户下单之后会安排用户最近货源发货,保证客户最快时间收到商品,那么当同一个地点存在多家经销商的时候使用什么分销策略呢,且分销策是可以灵活变更的,且如果经销商新注册或者合同解约了又怎么处理呢。
设计目的:策略模式的目的就是为了将多个策略独立管理,策略与策略之间互不影响干扰,且策略的增删改不会营销到原有代码,做到无侵入性。(意思就是后续业务发生拓展,比如引进了小米支付,京东支付,但是我们不需要修改调用支付的地方,只是新增小米支付、京东支付实现而已)。
图解:
另外这里模拟了一个聚合支付的场景,代码非常简单,我们模拟将第三方支付平台信息保存在Map(内存中),然后通过上述流程进行创建我们的支付策略(这里也就是具体的支付平台)。这样做以后即使有第三方支付平台接入进来我们也不会修改原先的Service层支付相关代码,侵入性相对较小,且维护方便,思路清晰。git地址:https://gitee.com/happy_bug/designMode/tree/master/src/main/java/com/likuncheng/strategy
另外这里提供一个企业级别的真实案例,就是上诉的需求分析中的第二个需求,分销就近发货功能,这个具体选择哪个分销商是根据不同的策略进行选择的。详情如下:
简单说一下需求,就是电商平台下单之后,订单会推送到我这里来,然后我根据经销商销售授权信息进行分配一个具体的经销商安排发货,但是如何安排需要单独剥离出来,方便后续维护以及拓展。这里关于如何选择经销商这一模块选择使用了策略模式
开始安排:
首先我们按照流程图第一步,我们需要一个ContextStrategy(策略上下文),提供实现,给Service进行调用,然后内部调用据图的策略:
/**
* 分销策略上下文 相当于中间人
*
* @author 李昆城
*/
@Component
public class ContextStrategy {
/**
* 所有的策略集合
*/
public static Map allStrategy = new HashMap<>(5);
/**
* 调用分销策略
*
* @param strategyCode 分销策略code
* @param allByProductTypeAndProvince 本次需要分销的集合
* @return 指定分销的经销商编号
*/
public String findDistributionAgencyStoreNo(String strategyCode, List allByProductTypeAndProvince) {
if (StringUtils.isEmpty(strategyCode) || CollectionUtils.isEmpty(allByProductTypeAndProvince)) {
throw new DIYException("参数不能为空");
}
DistributionStrategy distributionStrategy = allStrategy.get(strategyCode);
return distributionStrategy.DistributionStrategy(allByProductTypeAndProvince);
}
}
详解:首先我们声明一个Map(allStrategy )用于保存所有的策略信息,当然也可以将关键信息持久化到数据库进行维护。key值呢,就是我们策略初始化的时候注入容器的名称,value值呢是我们的策略接口,findDistributionAgencyStoreNo方法就是我们的分销选择经销商的方法,可以看到实现就是入参我们需要的参数,然后使用策略接口进行调用策略实现(这里保存的是接口,实际上调用方法会调用到据图的实现类的方法“里氏替换原则”),就这样我们就简单的实现了使用策略上下文调用具体的策略,且不用修改代码
接下来我们看看我们的Service层是如何调用我们的上下文的:
我们简单的分三步走
1:注入策略上下文
@Autowired
private ContextStrategy contextStrategy;
2:获取本次使用的策略关键字(也就是我们保存在Map里面的key值)
/**
* 需要使用的分销策略code码
*/
@Value("${distributionStrategyCode}")
private String distributionStrategyCode;
3:调用上下文方法实现分销发货经销商的选择
//调用分销策略
String agencyStoreNo = contextStrategy.findDistributionAgencyStoreNo(distributionStrategyCode, saleAuthorizations);
附:我们是把策略的code来源保存在Yml配置文件中的
-----配置文件
#分销策略编码
#firstByList:取值第一个经销商作为分销经销商
distributionStrategyCode: firstByListStrategy
就这样简单,我们便在Service进行了调用我们的上下文的获取分销商编号的方法,而该方法又通过我们的参数获得了具体的分销策略实现。这样以后有新的分销商新增或者删除,又或者需要更换获取分销商的实现(新策略),我们就不需要修改service层代码,只需要新增策略的实现,然后在配置文件指定需要使用的策略Code。
ok,现在我们已经把我们的流程图中的Service以及ContextStrategy之间的关系,以及如何调用搞清楚了,接下来我们看看我们的分销接口以及分销实现。
首先看看接口怎么定义的:
/**
* 分销策略
*
* @author 李昆城
*/
public interface DistributionStrategy {
/**
* 分销策略
*
* @param allByProductTypeAndProvince 所有的按照授权地区以及分销产品查询到的授权信息
* @return 经销商编号
*/
String DistributionStrategy(List allByProductTypeAndProvince);
/**
* 策略介绍
*
* @param name 策略名
* @param description 策略描述
*/
default void show(String name, String description) {
synchronized (this) {
System.out.println("\n\t策略名称:" + name + "\n\t" + "策略描述:" + description + "\n\t");
}
}
}
我们的策略接口,很简单,就是一个一个接口,负责获取分销商的编号,且加上一个默认实现(jdk8新特性,可以声明默认方法以及静态方法,默认方法实现类可以直接调用,静态方法按照静态方法模式调用)。
接下来我们看看我们一个简单的策略“获取第一个分销商”:
/**
* 分销策略-list的第一个
*
* @author 李昆城
*/
@Service
@SuppressWarnings("unused")
public class FirstByListStrategy implements DistributionStrategy {
/**
* 初始化的时候将自己注入到容器(所有的分销策略)中
*/
@PostConstruct
public void init() {
final String strategyName = "firstByListStrategy";
final String strategyDescription = "该策略永远都会选择所有符合条件的分销商列表中的第一个分销商进行分销订单商品";
FirstByListStrategy firstByList = (FirstByListStrategy) SpringUtils.getBean(strategyName);
if (Objects.isNull(ContextStrategy.allStrategy.get(strategyName))) {
ContextStrategy.allStrategy.put(strategyName, firstByList);
}
show(strategyName, strategyDescription);
}
/**
* 具体的分销实现
*
* @param allByProductTypeAndProvince 所有的按照授权地区以及分销产品查询到的授权信息
* @return 分销的经销商id
*/
@Override
public String DistributionStrategy(List allByProductTypeAndProvince) {
if (CollectionUtils.isEmpty(allByProductTypeAndProvince)) {
throw new DIYException("参数不能为空");
}
return allByProductTypeAndProvince.get(0).getAgencyStoreNo();
}
}
这个策略很简单,就是永远选择查询到的所有分销商的第一个进行安排分销。但是值得注意的是,在策略初始化完成之后,我们将自己的信息注入到了ContextStrategy中的Map中,好让ContextStrategy可以通过Code获取到自己的实例,为什么需要在初始化完成的时候在进行注入到ContextStrategy中呢,为了避免我们取出来的时候是一个空对象。
另外这里附上SpringUtils(获取Spring中注入的bean):
/**
* spring工具类,获取注入bean
*
* @author 李昆城
*/
@Component
@SuppressWarnings("all")
public class SpringUtils implements ApplicationContextAware {
/**
* spring上下文
*/
private static ApplicationContext applicationContext;
/**
* 获取applicationContext
*
* @param applicationContext application上下文
* @throws BeansException 查找bean时报错
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (Objects.isNull(SpringUtils.applicationContext)) {
this.applicationContext = applicationContext;
}
}
/**
* 根据名称获取bean
*
* @param name bean名
* @return 注入容器的bean
*/
public static Object getBean(String name) {
return applicationContext.getBean(name);
}
/**
* 根据class文件获取bean
*
* @param tClass class文件
* @return bean
*/
public static Object getBean(Class tClass) {
return applicationContext.getBean(tClass);
}
/**
* 根据名称以及class文件获取bean
*
* @param tClass class文件
* @param name 名称
* @return bean
*/
public static Object getBean(Class tClass, String name) {
return applicationContext.getBean(tClass, name);
}
}
ok,到了这里在联系我们的流程图,是不是就对策略模式有一个大概的理解了呢,可以尝试着写一个简单的demo,有些东西光看是不一定记住的,还是需要自己多加练习。