入职新公司一年多的时间都没写博客了额,有点久。
今天要说的是策略模式,策略模式定义了一系列的算法,并将每一个算法封装起来,而且使他们可以相互替换,让算法独立于使用它的客户而独立变化。
定义还是比较简单的,难点在于使用。
首先,怎么决定使用哪个策略呢?网上的教程大部分都是if/else,我就想说,在策略模式中还大量使用if/else的怎么没被打死呢???
那如果不用if/else该怎么办呢,答案就是map。仔细想想,if/else的目的是什么,就是为了拿到一种策略,那么map.get(key)不也能达到吗?这样是不是更优雅一些?
问题来了,map中怎么会有各种策略呢?这个就是我们注册的了。在项目启动的时候我们就会把这些策略注册到map中,这样在使用的时候就简单了,有些同学可能会说,这样不是浪费内存吗?其实这个担心是有道理的,但是如果这个策略对象的内存大小可以忽略不计,就没有有必要担心了。实际上策略模式的对象所占内存就是很小的,因为策略模式本身是不携带数据的,只是一个策略(方法)。这种map在spring、mybatis中也被大量使用着的。连大厂都是这么做的,我们效仿一下又如何呢?
上面说了这么多理论,下面给大家展示一下我现在项目中的一个策略模式的应用。场景是:我们需要从天猫、淘宝、京东、小红书、有赞等等六七十个平台拉取订单,保存到我们自己的数据库中,然后业务再去做统一的处理。策略模式在这里的应用就是,不同的平台会有不一样的拉取策略,每个平台提供的接口也不一样。
首先我们定义了一个公共的接口,定义了一个获取执行器名称的方法。
/**
* 执行器
*/
public interface Executor {
/**
* 获取具体的执行器名称
*
* @return
*/
String getName();
}
然后又定义了一个子接口,定义了几种拉取的方法
/**
* 导入订单的执行器
*/
public interface ImportExecutor extends Executor {
/**
* 导入订单
*/
void importOrderList();
/**
* 导入订单
* @param syncDate 同步时间
*/
void importOrderList(Date syncDate);
/**
* 导入订单
*
* @param pageNo
* @param syncDate
* @return
*/
boolean importOrderList(int pageNo, Date syncDate);
/**
* 根据平台订单号下载单个订单
* @param platformSoCode
*/
void importOrderByCode(String platformSoCode);
}
再然后就是重点了,给出了一个抽象的执行器
/**
* 抽象订单导入执行器
*/
public abstract class AbstractImportExecutor implements ImportExecutor {
protected final Logger logger = LoggerFactory.getLogger(getClass());
/**
* 精华的方法,统一注册
*/
@PostConstruct
public void register() {
//注意下面的方法中的this,一定是在运行的时候的子类
ExecutorRegistryUtil.registerImportExecutor(getName(), this);
}
@Override
public void importOrderList() {
if(!omsConstantConfig.getInvokeRemote()) {
return ;
}
//上一次拉取时间
Date syncDate=null;
try {
if (!importOrderList(1, syncDate)) {
return;//如果本次拉取不成功,则不更新拉取时间
}
Update(nowDate);//更新拉取时间为当前时间
} catch (Exception e) {
logger.error("第三方的下载订单接口出现异常,平台[{}]订单下载失败,同步时间保持不变,error:{}", getName(),e.getMessage());
return;//发生异常也不更新拉取时间
}
}
//公共的休眠方法,用于缓冲接口,不要频繁调用
protected void sleep(long milliseconds) {
try {
TimeUnit.MILLISECONDS.sleep(milliseconds);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 根据指定的日期范围导入订单
* @param syncDate 上一次的导入时间
*/
@Override
public void importOrderList(Date syncDate) {
importOrderList(1, syncDate);
};
}
然后就是各个平台的子类了,反倒是简单了,截取一个天猫的瞅瞅
/**
* 天猫导入Executor
*/
@Component
public class TmallImportExecutor extends AbstractImportExecutor {
private static final int PAGE_SIZE = 50;
@Override
public void importOrderByCode(String platformSoCode) {
//调用天猫的接口拉取订单
}
@Override
public String getName() {
return TppPlatformEnum.TMALL.getKey();
}
@SuppressWarnings("unchecked")
@Override
public boolean importOrderList(int pageNo, Date syncDate) {
//调用天猫的接口拉取订单
return true;
}
}
最后一个类就是怎么获取具体的执行器了
/**
* 执行器路由类
*
*/
@Component
public class ExecutorRouter {
/**
* 获取指定的订单处理器
*
* @param platformShopCode
* @return
*/
public OrderExecutor getOrderExecutor(String platformShopCode) {
check(platformShopCode);
return ExecutorRegistryUtil.getOrderExecutor(platformShopCode);
}
private void check(String platformShopCode) {
if (StringUtil.isEmpty(platformShopCode)) {
throw new BizException("平台名称不能为空");
}
}
}
至于这几个类中的ExecutorRegistryUtil就是一个对map的再次封装,不用过于关心,实际项目中,可以使用一个final static map代替