草庐IT

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

buguge - Keep it simple,stupid 2023-04-15 原文

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

本文说什么呢?通过一个程序案例来聊聊程序里随处可见的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

 

案例介绍完毕。

 

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

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

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

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

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

 

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

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?为什么要定义interface?定义interface是为了什么?你用对interface了吗?

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

 

有关程序里随处可见的interface,真的有用吗?真的用对了吗?的更多相关文章

  1. ruby - 在 Ruby 程序执行时阻止 Windows 7 PC 进入休眠状态 - 2

    我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0

  2. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  3. ruby - 在 Ruby 中编写命令行实用程序 - 2

    我想用ruby​​编写一个小的命令行实用程序并将其作为gem分发。我知道安装后,Guard、Sass和Thor等某些gem可以从命令行自行运行。为了让gem像二进制文件一样可用,我需要在我的gemspec中指定什么。 最佳答案 Gem::Specification.newdo|s|...s.executable='name_of_executable'...endhttp://docs.rubygems.org/read/chapter/20 关于ruby-在Ruby中编写命令行实用程序

  4. ruby-on-rails - Rails 应用程序之间的通信 - 2

    我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

  5. ruby - 无法运行 Rails 2.x 应用程序 - 2

    我尝试运行2.x应用程序。我使用rvm并为此应用程序设置其他版本的ruby​​:$rvmuseree-1.8.7-head我尝试运行服务器,然后出现很多错误:$script/serverNOTE:Gem.source_indexisdeprecated,useSpecification.Itwillberemovedonorafter2011-11-01.Gem.source_indexcalledfrom/Users/serg/rails_projects_terminal/work_proj/spohelp/config/../vendor/rails/railties/lib/r

  6. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

    刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

  7. ruby-on-rails - 如何在我的 Rails 应用程序 View 中打印 ruby​​ 变量的内容? - 2

    我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby​​中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R

  8. ruby - 即时确定方法的可见性 - 2

    我正在编写一个方法,它将在一个类中定义一个实例方法;类似于attr_accessor:classFoocustom_method(:foo)end我通过将custom_method函数添加到Module模块并使用define_method定义方法来实现它,效果很好。但我无法弄清楚如何考虑类(class)的可见性属性。例如,在下面的类中classFoocustom_method(:foo)privatecustom_method(:bar)end第一个生成的方法(foo)必须是公共(public)的,第二个(bar)必须是私有(private)的。我怎么做?或者,如何找到调用我的cust

  9. ruby - 是否有用于序列化和反序列化各种格式的对象层次结构的模式? - 2

    给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最

  10. ruby - 检查是否通过 require 执行或导入了 Ruby 程序 - 2

    如何检查Ruby文件是否是通过“require”或“load”导入的,而不是简单地从命令行执行的?例如:foo.rb的内容:puts"Hello"bar.rb的内容require'foo'输出:$./foo.rbHello$./bar.rbHello基本上,我想调用bar.rb以不执行puts调用。 最佳答案 将foo.rb改为:if__FILE__==$0puts"Hello"end检查__FILE__-当前ruby​​文件的名称-与$0-正在运行的脚本的名称。 关于ruby-检查是否

随机推荐