0
点赞
收藏
分享

微信扫一扫

程序里随处可见的interface,真的有用吗?真的用对了吗?

这两天在和一小伙伴研究解决RabbitMQ集群重启慢导致Consumer自动重连超时的问题,已经有了解决方案。接下来需要做个整理。由于同时涉及到springboot自动配置、springboot-amqp、spring-rabbit等诸多技术,先往后拖一下。

程序里随处可见的interface,真的有用吗?真的用对了吗?_发送消息

本文说什么呢?通过一个程序案例来聊聊程序里随处可见的interface。

先来个四连问:什么情况下定义interface?为什么要定义interface?定义interface是为了什么?你用对interface了吗?

 

接下来看这个案例吧。
程序里使用了RabbitMQ,下面 MQSender 是个interface,定义了生产者往mq放消息的两种方式:

package com.yft.rabbitmq.service;

import com.yft.rabbitmq.constant.BindingEnum;

/**
* 延迟发送服务类
*
* @author liuhongjie hongjie.liu@serviceshare.com
* @date 2022年06月12日
*/
public interface MQSender {

/**
* 发送消息
*
* @param bindingEnum 声明binding的enum
* @param msg 推送的消息
* @param delaySeconds 延迟的时间,秒
*/
void sendDelayMsg(BindingEnum bindingEnum, Object msg, int delaySeconds);

/**
* 发送消息
*
* @param bindingEnum 声明binding的enum
* @param msg 推送的消息
*/
void sendMsg(BindingEnum bindingEnum, Object msg);
}

其中 BindingEnum 是个枚举,封装定义了exchange和queue及两者的binding关系

package com.yft.rabbitmq.constant;

public enum BindingEnum {

SYNC_REVIEW_RECORD("sync-review-record", "sync-review-record", "sync-review-record"),
PAY_SETTLE("pay-settle","pay-settle","pay-settle"),
PAY_SETTLE_QUERY_DELAY("pay-settle-query-delay", "pay-settle-query-delay", "pay-settle-query-delay"),
;
String exchangeName;
String queueName;
String routingKey;

private static final String EXCHANGE_NAME_PREFIX = "exchange.levy-platform.";
private static final String QUEUE_NAME_PREFIX = "queue.levy-platform.";
private static final String ROUTING_KEY_PREFIX = "bindingKey.levy-platform.";

BindingEnum(String exchangeName, String queueName, String routingKey) {
this.exchangeName = exchangeName;
this.queueName = queueName;
this.routingKey = routingKey;
}

public String getExchangeName() {
return EXCHANGE_NAME_PREFIX + exchangeName;
}

public String getQueueName() {
return QUEUE_NAME_PREFIX + queueName;
}

public String getRoutingKey() {
return ROUTING_KEY_PREFIX + routingKey;
}
}

View Code

MQSender只有一个实现类 DefaultMQSender 。我们同样贴出来它的代码

package com.yft.rabbitmq.service;

import com.yft.rabbitmq.constant.BindingEnum;
import lombok.RequiredArgsConstructor;
import org.springframework.amqp.core.AmqpTemplate;

/**
* 延迟发送的默认实现
*
* @author liuhongjie hongjie.liu@serviceshare.com
* @date 2022年06月12日
*/
@RequiredArgsConstructor
public class DefaultMQSender implements MQSender {

private final AmqpTemplate amqpTemplate;

@Override
public void sendDelayMsg(BindingEnum bindingEnum, Object msg, int delaySeconds) {
amqpTemplate.convertAndSend(bindingEnum.getExchangeName(), bindingEnum.getRoutingKey(), msg, message -> {
message.getMessageProperties().setDelay(delaySeconds * 1000);
return message;
});
}

@Override
public void sendMsg(BindingEnum bindingEnum, Object msg) {
amqpTemplate.convertAndSend(bindingEnum.getExchangeName(), bindingEnum.getRoutingKey(), msg);
}
}

View Code

使用的话,见下面的 MQSenderConfig,它定义了相关的bean

package com.cn.yft.config;

import com.yft.rabbitmq.service.DefaultMQSender;
import com.yft.rabbitmq.service.MQSender;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* @Author wjx
* @Date 2022/7/6
*/
@Configuration
public class MQSenderConfig {

@Autowired
private AmqpTemplate rabbitTemplate;

@Bean
public MQSender mqSender() {
return new DefaultMQSender(rabbitTemplate);
}
}

View Code

 

案例介绍完毕。

 

程序里随处可见的interface,真的有用吗?真的用对了吗?_ide_02

那么,MQSender 这个interface的作用是什么?

当事人回答:作用是我能很方便的看到这个接口的能力。
当事人回答:如果以后不用RabbitMQ,新的消息中间件直接实现这个interface就行了。

如此几次对答后,几分钟后,小伙子开始觉得这个interface好像意义并不明显。

以这个程序实现场景来看,去掉这个interface是可以的,反而还会增强程序易读性。

那么,以这个场景来说,怎么定义一个合理的interface呢?
我画了下面的草图,图样图森破。可爱的小伙立即提出了新的疑惑,我当然明白他的疑惑。秉承我的风格,我并没有继续阐开,而是让这小伙后续琢磨琢磨。

程序里随处可见的interface,真的有用吗?真的用对了吗?_spring_03

 

程序里随处可见的interface,真的有用吗?真的用对了吗?_spring_04

好,在这里,我揭晓我的想法。

MQSender 摇身一变成:

package com.yft.rabbitmq.service;

import com.yft.dto.MQMsgModel;

/**
* 延迟发送服务类
*/
public interface MQSender {
/**
* 发送消息
* @param mqMsg mq消息对象
*/
void sendMsg(MQMsgModel mqMsg);
}

它的两个实现类:DefaultMQSender 是实时发送消息,DelayMQSender 是延迟发送消息 


package com.yft.rabbitmq.service;

/**
* 即时发送消息的实现
*/
@RequiredArgsConstructor
public class DefaultMQSender implements MQSender {

private final AmqpTemplate amqpTemplate;

@Override
public void sendMsg(MQMsgModel mqMsgModel) {
BindingEnum bindingEnum = mqMsgModel.getBindingEnum();
amqpTemplate.convertAndSend(bindingEnum.getExchangeName(), bindingEnum.getRoutingKey(), mqMsgModel.getMsg());
}
}


 


package com.yft.rabbitmq.service;

/**
* 延迟发送消息的实现
*/
@RequiredArgsConstructor
public class DelayMQSender implements MQSender {

private final AmqpTemplate amqpTemplate;

@Override
public void sendMsg(MQMsgModel mqMsgModel) {
BindingEnum bindingEnum = mqMsgModel.getBindingEnum();
amqpTemplate.convertAndSend(bindingEnum.getExchangeName(), bindingEnum.getRoutingKey(), mqMsgModel.getMsg(), message -> {
message.getMessageProperties().setDelay(mqMsgModel.getDelaySeconds() * 1000);
return message;
});
}
}


 

注意到多了一个 MQMsgModel, 好,我们来看这个 MQMsgModel,它是一个数据传输对象,定义了mq消息的属性

package com.yft.dto;

import com.yft.rabbitmq.constant.BindingEnum;
import lombok.Data;

/**
* mq消息对象
*/
@Data
public class MQMsgModel{
private BindingEnum bindingEnum;
private Object msg;
/**
* 指定消息的延迟时间,单位:秒 →→→→(非延迟消息,不用指定)
*/
private Integer delaySeconds;
}

使用的话, MQSenderConfig 定义两个bean就OK了

package com.cn.yft.config;

@Configuration
public class MQSenderConfig {

@Autowired
private AmqpTemplate rabbitTemplate;

@Bean
public MQSender mqSender() {
return new DefaultMQSender(rabbitTemplate);
}

@Bean
public MQSender delayMqSender() {
return new DelayMQSender(rabbitTemplate);
}
}

 

(完毕)

程序里随处可见的interface,真的有用吗?真的用对了吗?_程序设计思想_05

再贴一下上面的四连问:什么情况下定义interface?为什么要定义interface?定义interface是为了什么?你用对interface了吗?

不知你是否有了一些答案?

 

举报

相关推荐

0 条评论