思考记录:抽象、连接工厂

1 抽象

        对“抽象”一直没有形成认知,只知道书上印着的“封装、继承、多态、抽象”,也只是认识这几个字。最近在做一个功能时,对面向对象和抽象有了自己的一些理解,在这里记录一下。

        功能背景很简单,基于Redis实现RabbitMQ消息重试。

        要实现这个需求,我们需要生产者发送消息时带一个唯一标识“MessageId”,以便让消费者知道自己是否在消费同一条消息,并记录消费次数。于是所有消息的实体类都会继承一个抽象类“AbstractMessage”,这个抽象类中只有一个字段“MessageId”,这样就不用每个消息实体类都手动声明这个属性了。

        但是明显还有一些缺陷,以下单为例,Controller接收一个订单实体类,前端会将包括用户名、订单号、商品ID、总金额等信息发送过来。此时想给MessageId赋值,要不前端在请求里带过来,要不后端手动赋值;等于每个要发送消息的地方,都需要人为地赋值,这看起来并不优雅。赋值的动作依然可以抽象出来,利用继承的特性“实例化子类时,会默认调用父类的无参构造器”,可以在构造器中生成随机数并赋值,抽象类就成了以下形式。

public abstract class AbstractMessage implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableField(exist = false)
    private String MessageId;

    AbstractMessage() {
        this.MessageId = IdWorker.get32UUID();
    }

}

        消息实体类就抽象完毕了,我们将所有共同的特征都提取了出来。

        继续思考,所有消费者都需要重试功能,那每个消费者方法都要写一遍重试方法,但其实所有重试的逻辑都是一致的——获取“MessageId”、去Redis中查询重试次数、次数未到上限则重试、达到上限则丢入死信队列;这个逻辑完全是可以抽象出来的,让所有消费方法类都继承消费方法抽象类“AbstractConsumer”,并在抽象类中写好所有消费者会用到的方法,如重试方法、分布式锁的获取和解锁等。

        但是抽象类中还有几点需要思考,首先抽象类要利用Redis来处理重试逻辑,而抽象类是不能实例化的,也就无法进行注入。这时就又可以利用继承的特性,子类使用构造器注入RedisUtil,并在构造器中调用父类的有参构造器,父类的构造器便能通过子类的构造器来对父类的RedisUtil进行赋值,就能在抽象类中顺利操作Redis。

        其次是抽象类重试方法的入参,子类调用父类公共方法时经常会存在一个尴尬的情景:子类的实现五花八门,但父类不在乎,父类指定好自己的职责就不管你们了,时常会出现不兼容。

        例如现在有两个消费方法,一个消费者接收订单信息、一个消费者接收付款信息,父类重试方法依赖实体类信息,那这两个消费者就会这样调用父类的方法。

//订单消费者
this.retryExecute(channel, message, seckillOrderInfo, headers);
//父类方法入参
public void retryExecute(Channel channel, Message message, SeckillOrderInfo seckillOrderInfo, Map<String, Object> headers) throws IOException {
    //Todo: 重试逻辑
}


//付款消费者
this.retryExecute(channel, message, payInfo, headers);
//父类方法入参
public void retryExecute(Channel channel, Message message, PayInfo payInfo, Map<String, Object> headers) throws IOException {
    //Todo: 重试逻辑
}

        难题出现了,每个消费者都有不同类型的实体类,但父类的方法是固定的,应该怎么指定才能做到两全其美,既能接收订单实体类,又能接收付款实体类呢?

        也有种思路是把公共的特征一个一个传进公共方法,比如上面提到的“MessageId”,我不传实体类了,我直接传字符串行不行?这样当然可以,但如果需要根据多个字段进行处理呢?比如我还需要传入时间、传入用户等等等等,难道每次都新增一个入参?

        答案就是使用消息抽象类进行使用,将所有共同特征抽象进消息抽象类,消费方法抽象类直接接收消息抽象类。

public void retryExecute(Channel channel, Message message, AbstractMessage abstractMessage, Map<String, Object> headers) throws IOException {
    //Todo: ...
}

         消费方法抽象类入参接收消息抽象类,此时只要你传入的是消息抽象类的子类,想获取什么就获取什么,要加字段直接在消息抽象类里加,在消费方法抽象类直接获取即可。因为所有子类都会向上转型为父类,所有父类的特征都是可见的,这种方式既优雅又保持了较小的改动量,一次修改便可以涉及所有消息和消费者。

       

        经过这样一个剥丝抽茧的过程,我对面向对象和特性有了新的理解。面向对象语言中万物都是对象, 他给予你无限的可能性。

  • 如果你需要相对安全闭塞的操作环境,封装一个类并使用防御性的权限修饰符(如private,protect)供信任的人所用。
  • 如果你需要相对灵活的方法,继承一个普通的类,使用其中封装好的public方法,并定义一些自己的方法;或是利用重写、重载来灵活调用其中的某些方法。
  • 如果你需要绝对灵活的能力,抽象出一些高度集中的特征点,创建一个抽象类并指定抽象方法(或接口),继承他并实现每个抽象方法为自己所用。

        比如一个美团外卖员,他继承抽象类外卖小哥,抽象类中有所有外卖员可能有的特性,如衣服颜色、头盔颜色、送外卖方法;在新增饿了么外卖员时只需要继承外卖小哥类,编译器会自动提示我们,什么这位小哥必须有的共性,什么是你可以随意发挥的特性。

2 连接工厂

        疑惑出现在引入并配置Redisson时,先介绍下Redisson的初始化过程:

  1. 配置Config,Redisson有一个特有的Config类,包含了各种Redis连接需要的属性。例如指定使用单体、主从、集群、哨兵模式,节点地址,最大最小连接数等。
  2. 调用Redisson.create()并将Config传入,生成一个RedissonClient;RedissonClient中有个ConnectionManager,其中记录了Redis服务端连接的信息,后续执行get、set等命令时都是从ConnectionManager中获取一个执行器来实现。
    这一步生成的RedissonClient已经可以操作Redis了,要是没有特殊要求,这一步结束Redisson就初始化完成了。
  3. 由于原有使用RedisTemplate的地方我们也要做到兼容,因此还需要配置RedissonConnectionFactory,让RedisTemplate也使用Redisson的连接工厂。因此要创建一个RedissonConnectionFactory,把上面创建好的RedissonClient塞进去,再指定RedisTemplate也使用这个工厂。
  4. 由于RedissonConnectionFactory父类是RedisConnectionFactory,因此RedisTemplate需要RedisConnection时也可以从其中取。

        在我看来“工厂”这个概念,就是里面放好了一堆堆成品,你想用了你就进去取;RedisTemplate指定了Redisson的工厂后,实例化时也会从该工厂中获取,也就共用了同一个Redis连接。

        然后问题就出现了,在我看来整个工厂里只有一个RedissonClient,跟源码能看出来连接是由ConnectionManager管理,同时配置了主从Redis的最大连接数和最小空闲连接数,并放进ConnectionPool里——那么这个工厂的意义是什么?真的就只是放一个RedissonClient,然后RedissonClient包办主从连接、空闲连接等所有管理吗?

        基于这个问题,去搜索了一下ConnectionFactory和ConnectionPool的区别。根据《Connection Pool and Connection factory difference???》(Connection Pool and Connection factory difference??? - Oracle Forums)这个问题的回复,答者的大意翻译过来就是,工厂放了许多不同特征的个体,例如苹果、香蕉、梨;而每个池子,只放置许多个同一个体。这个答案是非常符合我对工厂和池的理解的,但现在看来整个RedissonConnectionFactory的作用就是放了一个大大的集成了工厂和池的Manager,那RedisTemplate又是怎么从中获取自己所需连接的呢?

        这个问题困扰很久了,希望路过的大佬能指点一下。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值