背景

在上一篇文章中,我们介绍了如何设计一个消息中心,传送门 👉《如何设计一个消息中心》

有了承载这些消息的地方后,接下来的问题便是,这些消息从哪里来?

通常对于一个内容型产品来说,在其互动体系中,为了增强消息的用户触达,增强用户的互动心智,在互动(评论、点赞等)行为发生后,会将互动消息推送至消息中心,然后根据不同的互动行为类型匹配不同的消息模版。

然而随着互动行为种类的增加(内容的点赞、评论的点赞……),不断的通过 if…else 来根据不同的消息类型生成不同的消息模版会使得业务代码愈发复杂,难以维护。

不仅如此,一旦需要增加一种新的互动消息时,需要对原有代码进行破坏性修改,违背了“开闭原则”。因此有必要对互动行为消息转发至消息中心这一场景进行抽象,让后续的维护者、建设者只需要关心某一特定的互动行为消息即可(我可不想未来被别人喷在 💩 山上拉 💩)。

策略模式

在说明具体的实现方案前,我们先介绍一个设计模式——策略模式。

策略模式,英文全称是 Strategy Design Pattern。在 GoF 的《设计模式》一书中,它是这样定义的:

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

翻译成中文就是:定义一簇算法类,将每个算法分别封装起来,让它们可以互相替换。策略模式可以使算法的变化独立于使用它们的客户端(这里的客户端代指使用算法的代码)。

策略模式用来解耦策略的定义、创建、使用。实际上,一个完整的策略模式就是由这三个部分组成的。

  • 策略类的定义比较简单,包含一个策略接口和一组实现这个接口的策略类。
  • 策略的创建由工厂类来完成,封装策略创建的细节。
  • 策略模式包含一组策略可选,客户端代码如何选择使用哪个策略,有两种确定方法:编译时静态确定和运行时动态确定。其中,“运行时动态确定”才是策略模式最典型的应用场景。

实现方案

在对策略模式有了基本的了解后,我们尝试在本节将其运用起来。

仔细分析了第一章的应用场景后我们发现其实实现链路并不复杂,整体流程如下图所示:

在本例中,根据不同的互动行为类型,我们将点赞消息和评论消息分成以下几类:

点赞类:

  1. 内容点赞
  2. 评论点赞

评论类:

  1. 内容评论
  2. 内容评论的回复

转发策略的定义

整个方案中最重要的一环是对转发策略的匹配,因此第一步我们要做的应该是定义一个策略。为了方便后续的扩展(未来可能会有多种转发策略),我们此处定义一个策略接口MsgTransmitStrategy

public interface MsgTransmitStrategy<T> {
  	// 是否命中策略
    boolean match(T message);

  	// 消息类型
    TransmitMsgType getMsgType();

  	// 创建需要转发的消息实体
    MessageContent createMessageContent(T message);
}

每个策略需要具备的行为能力应该有:

  • 明确自己是否命中了转发策略:match(T message)
  • 明白自己要转发的是什么类型的消息:getMsgType()
  • 创建要转发的消息:createMessageContent(T message)

转发策略的创建

以点赞消息为例,上文提到存在两种点赞消息的转发策略:内容点赞与评论点赞。

因此我们需要创建两个具体的策略:ContentLikeMsgTransmitStrategyCommentLikeMsgTransmitStrategy

@Component
public class ContentLikeMsgTransmitStrategy implements MsgTransmitStrategy<String> {
    @Override
    public TransmitMsgType getMsgType() {
        return TransmitMsgType.CONTENT_LIKE;
    }

    @Override
    public boolean match(String message) {
        // do something
      	return true;
    }

    @Override
    public MessageContent createMessageContent(String message) {
				// do something……………
        return messageContent;
    }
}

@Component
public class CommentLikeMsgTransmitStrategy implements MsgTransmitStrategy<String> {
    @Override
    public TransmitMsgType getMsgType() {
        return TransmitMsgType.COMMENT_LIKE;
    }

    @Override
    public boolean match(String message) {
        // do something
      	return true;
    }

    @Override
    public MessageContent createMessageContent(String message) {
				// do something……………
        return messageContent;
    }
}

转发策略的使用

现在,我们已经创建了几种消息转发策略了,那么客户端代码一般如何确定使用哪个策略呢?最常见的是运行时动态确定使用哪种策略,这也是策略模式最典型的应用场景。

这里的“运行时动态”指的是,我们事先并不知道会使用哪个策略,而是在程序运行期间,根据配置、用户输入、计算结果等这些不确定因素,动态决定使用哪种策略。

我们来创建一个MsgTransmitExecutor作为策略的执行器,通过遍历的手段依次调用每个策略的 match 方法,符合条件的策略类可以执行统一的转发方法。

@Component
public class MsgTransmitExecutor<T> {

    @Resource
    private MsgTransmitTools msgTransmitTools;

    @Resource
    private BentleyManager bentleyManager;

    public void execute(T message, List<MsgTransmitStrategy<T>> strategyList) {
        for (MsgTransmitStrategy<T> strategy : strategyList) {
          	// 依次调用策略的match方法
            if (strategy.match(message)) {
                MessageContent messageContent = strategy.createMessageContent(message);
                Optional.ofNullable(messageContent)
                        .filter(mc -> msgTransmitTools.isNeedSendPush(mc))
                        .ifPresent(mc -> bentleyManager.sendMessage(mc));
            }
        }
    }
}

那么,对于客户端(实际调用策略的类),只需通过组合(而非继承)的方式,将策略的执行器注入到具体的消息处理逻辑中即可。

public class LikeMsgListener extends MessageConsumer<String> {

    /**
     * 点赞消息转发策略集合
     */
    private final List<MsgTransmitStrategy<String>> LIKE_MSG_TRANSMIT_STRATEGY_LIST = Lists.newArrayList();

    @Resource
    private MsgTransmitExecutor<String> msgTransmitExecutor;

    @Resource
    private ContentLikeMsgTransmitStrategy contentLikeMsgTransmitStrategy;

    @Resource
    private CommentLikeMsgTransmitStrategy commentLikeMsgTransmitStrategy;

    @PostConstruct
    public void init() {
        LIKE_MSG_TRANSMIT_STRATEGY_LIST.addAll(Arrays.asList(
                contentLikeMsgTransmitStrategy,
                commentLikeMsgTransmitStrategy
        ));
    }

    @Override
    protected ConsumeConcurrentlyStatus processMessage(String message, MessageExt messageExt) {
        try {
            msgTransmitExecutor.execute(message, LIKE_MSG_TRANSMIT_STRATEGY_LIST);
        } catch (Exception e) {
            PgLog.messageBoxError("LikeListener", e);
        }

        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    }
}

总结

在上述实例中,通过 LikeMsgListener 监听点赞类消息。通过 MsgTransmitStrategy 接口定义消息转发至消息中心的行为策略,MsgTransmitExecutor 作为策略的执行器最终实现将匹配过后的消息以不同的模版类型推送至消息中心。

整个方案,通过利用策略模式避免了整个转发场景中使用多重条件判断,维护者只需专注于当前的转发策略即可,遵循了“开闭原则”,同时通过组合而非继承的方式注入策略执行器,扩展性较好。

但任何事物都具有两面性,一旦未来的场景变得更加复杂(例如点赞行为可以支持更多类型),转发策略类会增多,届时就需要考虑使用一些混合模式(例如策略也可以使用工厂模式创建等方法),解决业务发展所带来的策略类膨胀的问题了。