设计模式的简单应用-策略模式、模板方法模式

621 阅读5分钟

背景

系统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),并且各分支实现逻辑(或者叫算法)类似,此时就可以考虑使用策略模式,避免多分支所带来的复杂和难以维护

优点

  • 算法可以自由的切换
  • 代码扩展性良好
  • 代码可读性高

缺点

  • 所维护的类会增多

模板方法模式

使用场景

  • 代码中可以抽象出固定的算法框架(或者流程),对不同的业务来说,实际发生的逻辑不同。这个时候就可以考虑将算法框架抽象成模板方法,可变的部分抽象成基本方法,由子类来实现基本方法

优点

  • 可扩展性好。封装不变部分(算法框架),扩展可变部分(由子类来实现具体行为逻辑)
  • 提取公共代码,便于维护

缺点

  • 每一个不同的实现都需要一个子类来实现,导致维护的类会增多