应用场景
假设有这样的业务场景,大数据系统把文件推送过来,根据不同类型采取不同的解析方式。多数的小伙伴就会写出以下的代码:
public class Question {
public static void main(String[] args) {
String type = "";
if (type == "A") {
//按照A格式解析
} else if (type == "B") {
//按B格式解析
} else {
//按照默认格式解析
}
}
}
存在问题
- 1、如果分支变多,这里的代码就会变得臃肿,难以维护,可读性低。
- 2、如果你需要接入一种新的解析类型,那只能在原有代码上修改。
说得专业一点的话,就是以上代码,违背了面向对象编程的开闭原则以及单一原则。
- 开闭原则(对于扩展是开放的,但是对于修改是封闭的):增加或者删除某个逻辑,都需要修改到原来代码
- 单一原则(规定一个类应该只有一个发生变化的原因):修改任何类型的分支逻辑代码,都需要改动当前类的代码。
解决方案
如果你的代码就是酱紫:有多个if…else等条件分支,并且每个条件分支,可以封装起来替换的,我们就可以使用策略模式来优化。
/**
* 策略模式:
* 一个接口或者抽象类,里面两个方法(一个方法匹配类型,一个可替换的逻辑实现方法)
* 不同策略的差异化实现(就是说,不同策略的实现类)
*/
public interface IFileStrategy {
/**
* 解析属于哪种文件类型,也可以继续优化
*
* @return 文件类型
*/
String getFileType();
/**
* 封装的公用算法(具体的解析方法)
*
* @param param
*/
void process(Object param);
}
@Component
public class AFileResolve implements IFileStrategy {
@Override
public String getFileType() {
return DbDriverClassNameTypeEnum.MYSQL.getType();
}
@Override
public void process(Object param) {
System.out.println("A类型文件解析具体逻辑");
}
}
@Component
public class BFileResolve implements IFileStrategy {
@Override
public String getFileType() {
return DbDriverClassNameTypeEnum.POSTGRES.getType();
}
@Override
public void process(Object param) {
System.out.println("B类型文件解析具体逻辑");
}
}
@Component
public class DefaultResolve implements IFileStrategy {
@Override
public String getFileType() {
return DbDriverClassNameTypeEnum.ORACLE.getType();
}
@Override
public void process(Object param) {
System.out.println("默认类型文件解析具体逻辑");
}
}
/**
* 如何使用呢?
* 我们借助spring的生命周期,使用ApplicationContextAware接口,把对用的策略,初始化到map里面。然后对外提供resolveFile方法即可。
* ApplicationContextAware接口能够轻松感知并在Spring中获取应用上下文,进而访问容器中的其他Bean和资源,
* 这增强了组件间的解耦,了代码的灵活性和可扩展性,是Spring框架中实现高级功能的关键接口之一。
*/
@Component
public class StrategyService implements ApplicationContextAware {
private Map<String, IFileStrategy> iFileStrategyMap = new ConcurrentHashMap<>();
/**
* 初始化策略.
*
* @param applicationContext Spring应用上下文
* @throws BeansException
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, IFileStrategy> tmepMap = applicationContext.getBeansOfType(IFileStrategy.class);
tmepMap.values().forEach(strategyService -> iFileStrategyMap.put(strategyService.getFileType(), strategyService));
}
/**
* 统一对外提供的接口,根据类型处理文件
*
* @param fileType
* @param objectParam
*/
public void resolveFile(String fileType, Object objectParam) {
IFileStrategy iFileStrategy = iFileStrategyMap.get(fileType);
if (iFileStrategy != null) {
iFileStrategy.process(objectParam);
}
}
}
继续延伸
在参数校验的场景中,经常会遇到根据某一个枚举字段的不同值来校验不同的入参字段,校验逻辑也是不同的,因此很容易通过IF ELSE来进行判断处理,但是这样的代码扩展性不强,CRUD痕迹很明显,不优雅!因此可以选择测罗模式进行优化。
public interface LinkageTriggerValidator {
void check(LinkageRuleTriggerVo triggerVo);
}
public class BizPeriodTypeValidator implements LinkageTriggerValidator {
@Override
public void check(LinkageRuleTriggerVo triggerVo) {
BizPeriodType eventType = triggerVo.getBizEventType();
if (null == eventType) {
throw new ParamCheckRuntimeException("业务周期事件配置缺少业务周期类型");
}
// todo
}
}
public class ReservationEventValidator implements LinkageTriggerValidator {
@Override
public void check(LinkageRuleTriggerVo triggerVo) {
ReservationEventType eventType = triggerVo.getReservationEventType();
if (null == eventType) {
throw new ParamCheckRuntimeException("预定事件配置缺少预定时间类型");
}
// todo
}
}
public abstract class LinkageTriggerValidatorContext {
/**
* 触发源校验器.
*/
private static Map<LinkageTriggerSource, LinkageTriggerValidator> triggerValidatorMap =
new EnumMap<>(LinkageTriggerSource.class);
static {
triggerValidatorMap.put(LinkageTriggerSource.RESERVE_TIME, new ReservationEventValidator());
triggerValidatorMap.put(LinkageTriggerSource.MANUAL_TRIGGER, new TriggerEventValidator());
triggerValidatorMap.put(LinkageTriggerSource.BIZ_PERIOD, new BizPeriodTypeValidator());
}
public static void check(LinkageTriggerSource type, LinkageRuleTriggerVo triggerVo) {
LinkageTriggerValidator validator = triggerValidatorMap.get(type);
if (null == validator) {
throw new UnsupportedOperationException("不支持该触发源类型:" + type);
}
validator.check(triggerVo);
}
}
使用LinkageTriggerValidatorContext提供的check方法,统一进行参数校验即可
LinkageTriggerValidatorContext.check(trigger.getType(), trigger);