背景
系统A需接收系统B发出的kafka消息(含有resource_type,resource_id等),经过过滤等操作后,转换成实际关心的资源操作(更新、上下架等)rabbit mq对外发送
第一版实现
代码实现
* 发送MQ
*
* @param ndrKafkaMessage kafka消息
* @param resource 资源元数据
* @param objectiveResourceType 资源类型编码
*/
private void sendMq(NdrKafkaMessage ndrKafkaMessage, ResourceMetaTiListViewModel resource, NdrResourceType objectiveResourceType) {
String tenantId = ndrKafkaMessage.getTenantId();
String containerId = ndrKafkaMessage.getContainerId();
String resourceId = ndrKafkaMessage.getResourceId();
//资源元数据变更
if (resourceUpdated(ndrKafkaMessage)) {
switch (objectiveResourceType) {
case objective_knowledge:
//实际逻辑
break;
case objective_knowledge_content:
//实际逻辑
break;
case objective:
//实际逻辑
break;
case objective_knowledge_type:
//实际逻辑
break;
case objective_entry_template:
//实际逻辑
break;
case objective_entry:
//实际逻辑
break;
case objective_type:
//实际逻辑
break;
default:
}
}
//关系变更
if (relationUpdated(ndrKafkaMessage)) {
switch (objectiveResourceType) {
case objective_knowledge:
//实际逻辑
break;
case objective:
//实际逻辑
break;
case objective_entry:
//实际逻辑
break;
default:
}
}
//状态变更
if (this.resourceStatusChanged(ndrKafkaMessage)) {
String status = ndrKafkaMessage.getStatus();
if (online(status)) {
//上架
switch (objectiveResourceType) {
case objective_entry:
//实际逻辑
break;
case objective:
//实际逻辑
break;
case objective_knowledge:
//实际逻辑
break;
case objective_type:
//实际逻辑
break;
case objective_entry_template:
//实际逻辑
break;
case objective_knowledge_type:
//实际逻辑
break;
case objective_knowledge_content:
//实际逻辑
default:
}
} else if (offline(status)) {
//下架
switch (objectiveResourceType) {
case objective_entry:
//实际逻辑
break;
case objective:
//实际逻辑
break;
case objective_knowledge:
//实际逻辑
break;
case objective_type:
//实际逻辑
break;
case objective_entry_template:
//实际逻辑
break;
case objective_knowledge_type:
//实际逻辑
break;
case objective_knowledge_content:
//实际逻辑
break;
default:
}
}
}
}
缺点
- 不符合开闭原则。如果要新增或删除一种资源类型的MQ,要直接改源码的case逻辑
- 不符合单一职责。一个发送MQ的方法中,包括了所有资源如何发送MQ的逻辑
- 代码可读性较差。代码方法的行数较长,超过了80行。并且如果我只关心objective类型资源的MQ逻辑,还需要一个个找到每个分支中对应的case,太麻烦了
使用设计模式优化后的实现
UML类图
代码实现
- 抽象出的策略接口
/**
* 将kafka消息转换成MQ消息发送策略接口
*/
public interface MqSenderStrategy {
/**
* 将NDR kafka消息转换成实际对应的MQ消息进行发送
*
* @param ndrKafkaMessage NDR kafka消息
* @param resource 资源元数据
*/
void sender(NdrKafkaMessage ndrKafkaMessage, ResourceMetaTiListViewModel resource);
/**
* 处理的实际资源类型
*
* @return NDR资源类型
*/
NdrResourceType handle();
}
- 利用模板方法模式抽象出的策略模板类
- sender方法是模板方法
- update、online、offline、relationChanged是基本方法,子类可根据实际需求对方法进行重写
/**
* MqSenderStrategy接口的一个模板方法 模板类
*/
public abstract class AbstractMqSenderStrategyTemplate implements MqSenderStrategy {
@Override
public final void sender(NdrKafkaMessage ndrKafkaMessage, ResourceMetaTiListViewModel resource) {
String containerId = ndrKafkaMessage.getContainerId();
String tenantId = ndrKafkaMessage.getTenantId();
//资源元数据变更
if (NdrKafkaMessageUtils.resourceUpdated(ndrKafkaMessage)) {
update(tenantId, containerId, resource);
}
//关系变更
if (NdrKafkaMessageUtils.relationUpdated(ndrKafkaMessage)) {
relationChanged(tenantId, containerId, resource, ndrKafkaMessage.getRelations());
}
//状态变更
if (NdrKafkaMessageUtils.resourceStatusChanged(ndrKafkaMessage)) {
String status = ndrKafkaMessage.getStatus();
//上架
if (NdrKafkaMessageUtils.online(status)) {
online(tenantId, containerId, resource);
} else if (NdrKafkaMessageUtils.offline(status)) {
offline(tenantId, containerId, resource);
}
}
}
/**
* 资源更新MQ,默认空实现,可根据实际需求重写该方法
*
* @param tenantId 租户id
* @param containerId 容器id
* @param resource 资源信息
*/
protected void update(String tenantId, String containerId, ResourceMetaTiListViewModel resource) {
}
/**
* 资源上架MQ,默认空实现,可根据实际需求重写该方法
*
* @param tenantId 租户id
* @param containerId 容器id
* @param resource 资源信息
*/
protected void online(String tenantId, String containerId, ResourceMetaTiListViewModel resource) {
}
/**
* 资源下架MQ,默认空实现,可根据实际需求重写该方法
*
* @param tenantId 租户id
* @param containerId 容器id
* @param resource 资源信息
*/
protected void offline(String tenantId, String containerId, ResourceMetaTiListViewModel resource) {
}
/**
* 资源关系变更MQ,默认空实现,可根据实际需求重写该方法
*
* @param tenantId 租户id
* @param containerId 容器id
* @param resource 资源信息
* @param relations 资源剩下关联的全量关系
*/
protected void relationChanged(String tenantId, String containerId, ResourceMetaTiListViewModel resource, List<NdrKafkaRelationMessage> relations) {
}
}
- 策略实现类
- 以objective资源类型举例,其他类型的实现类大同小异(主要的差别在于,部分实现类不重写relationChanged方法,只需要重写update、online、offline方法)
/**
* 学习目标发送MQ策略实现类
*/
@Service
public class ObjectiveMqSenderStrategy extends AbstractMqSenderStrategyTemplate {
@Resource
private ObjectiveMqSender objectiveMqSender;
@Override
protected void update(String tenantId, String containerId, ResourceMetaTiListViewModel resource) {
//实际逻辑
}
@Override
protected void online(String tenantId, String containerId, ResourceMetaTiListViewModel resource) {
//实际逻辑
}
@Override
protected void offline(String tenantId, String containerId, ResourceMetaTiListViewModel resource) {
//实际逻辑
}
@Override
protected void relationChanged(String tenantId, String containerId, ResourceMetaTiListViewModel resource, List<NdrKafkaRelationMessage> relations) {
//实际逻辑
}
@Override
public NdrResourceType handle() {
return NdrResourceType.objective;
}
}
- MQ消息发送策略的上下文类
/**
* MQ消息发送策略的上下文类
*/
@Component
public class MqSenderStrategyContext {
@Autowired
private List<MqSenderStrategy> mqSenderStrategyList;
/**
* 资源类型 -> 对应的MQ发送策略实现类
*/
private Map<NdrResourceType, MqSenderStrategy> resourceMqSenderStrategyMap;
@PostConstruct
private void initResourceMqSenderStrategyMap() {
this.resourceMqSenderStrategyMap = mqSenderStrategyList.stream().collect(Collectors.toMap(MqSenderStrategy::handle, Function.identity()));
}
/**
* 获取资源对应的MQ发送策略实现类
*
* @param ndrResourceType 资源类型
* @return 对应的MQ发送策略实现类
*/
public MqSenderStrategy getMqSenderStrategy(NdrResourceType ndrResourceType) {
return resourceMqSenderStrategyMap.get(ndrResourceType);
}
}
- 业务使用方
//省略无关的前置逻辑
MqSenderStrategy mqSenderStrategy = mqSenderStrategyContext.getMqSenderStrategy(objectiveResourceType);
if (mqSenderStrategy == null) {
return;
}
mqSenderStrategy.sender(ndrKafkaMessage, resource);
优点
- 代码职责清晰。每个实现子类只关注一类资源的MQ发送逻辑
- 符合开闭原则。如果新增一种资源类型,只需要新增一个实现子类即可
- 代码灵活性高。每个实现子类可根据实际需求,重写不同的方法(update、online等)
- 代码可读性高。如果想知道objective的MQ发送逻辑,直接找到对应的实现类即可
关于策略模式、模板方法模式的思考
策略模式
使用场景
- 代码存在很多个分支(if...else或switch),并且各分支实现逻辑(或者叫算法)类似,此时就可以考虑使用策略模式,避免多分支所带来的复杂和难以维护
优点
- 算法可以自由的切换
- 代码扩展性良好
- 代码可读性高
缺点
- 所维护的类会增多
模板方法模式
使用场景
- 代码中可以抽象出固定的算法框架(或者流程),对不同的业务来说,实际发生的逻辑不同。这个时候就可以考虑将算法框架抽象成模板方法,可变的部分抽象成基本方法,由子类来实现基本方法
优点
- 可扩展性好。封装不变部分(算法框架),扩展可变部分(由子类来实现具体行为逻辑)
- 提取公共代码,便于维护
缺点
- 每一个不同的实现都需要一个子类来实现,导致维护的类会增多