
5.5 抽象工厂模式重构代码
接下来使用抽象工厂模式优化代码,也是一次代码重构。在前文介绍过,抽象工厂的实质就是用于创建工厂的工厂。可以理解为有三个物料加工车间,其中任意两个都可以组合出一个新的生产工厂,用于装备汽车或缝纫机。另外,这里会使用代理类的方式实现抽象工厂的创建过程。而两个 Redis 集群服务相当于两个车间,两个车间可以构成两个工厂。通过代理类的实现方式,可以非常方便地实现Redis服务的升级,并且可以在真实的业务场景中做成一个引入的中间件,给各个需要升级的系统使用。这里还有非常重要的一点,集群EGM和集群IIR在部分方法提供上略有不同,如方法名和参数,因此需要增加一个适配接口。最终使用这个适配接口承接两套集群服务,做到统一的服务输出。
5.5.1 工程结构


整个抽象工厂代码类关系如图5-4所示。

图5-4
结合以上抽象工厂的工程结构和类关系,简要介绍这部分代码包括的核心内容。整个工程包结构分为三块:工厂包(factory)、工具包(util)和车间包(workshop)。
·工厂包:JDKProxyFactory、JDKInvocationHandler两个类是代理类的定义和实现,这部分代码主要通过代理类和反射调用的方式获取工厂及方法调用。
·工具包:ClassLoaderUtils类主要用于支撑反射方法调用中参数的处理。
·车间包:EGMCacheAdapter、IIRCacheAdapter两个类主要是通过适配器的方式使用两个集群服务。把两个集群服务作为不同的车间,再通过抽象的代理工厂服务把每个车间转换为对应的工厂。这里需要强调一点,抽象工厂并不一定必须使用目前的方式实现。这种使用代理和反射的方式是为了实现一个中间件服务,给所有需要升级 Redis 集群的系统使用。在不同的场景下,会有很多不同的变种方式实现抽象工厂。
5.5.2 定义集群适配器接口

适配器接口的作用是包装两个集群服务,在前面已经提到这两个集群服务在一些接口名称和入参方面各不相同,所以需要进行适配。同时在引入适配器后,也可以非常方便地扩展。
5.5.3 实现集群适配器接口
1.EGM集群:EGMCacheAdapter

2.IIR集群:IIRCacheAdapter

如果是两个集群服务的统一包装,可以看到这些方法名称或入参都已经统一。例如,IIR集群的iir.setExpire和EGM集群的egm.setEx都被适配成一个方法名称——set方法。
5.5.4 代理方式的抽象工厂类
1.代理抽象工厂JDKProxyFactory

这里有一点非常重要,就是为什么选择代理方式实现抽象工厂。
因为要把原单体Redis服务升级为两套 Redis 集群服务,在不破坏原有单体Redis服务和实现类的情况下,也就是cn-bugstack-design-5.0-0 的 CacheServiceImpl,通过一个代理类的方式实现一个集群服务处理类,就可以非常方便地在Spring、SpringBoot等框架中通过注入的方式替换原有的CacheServiceImpl实现。这样中间件设计思路的实现方式具备了良好的插拔性,并可以达到多组集群同时使用和平滑切换的目的。
getProxy方法的两个入参的作用如下:
·Class cacheClazz:在模拟的场景中,不同的系统使用的 Redis 服务类名可能有所不同,通过这样的方式便于实例化后的注入操作。
·Class<?extends ICacheAdapter>cacheAdapter:这个参数用于决定实例化哪套集群服务使用Redis功能。
2.反射调用方法JDKInvocationHandler

这部分是工厂被代理实现后的核心处理类,主要包括如下功能:
·相同适配器接口 ICacheAdapter 的不同 Redis 集群服务实现,其具体调用会在这里体现。
·在反射调用过程中,通过入参获取需要调用的方法名称和参数,可以调用对应Redis集群中的方法。
抽象工厂搭建完成了,这部分抽象工厂属于从中间件设计中抽取出来的最核心的内容,如果需要在实际的业务中使用,则需要扩充相应的代码,如注入的设计、配置的读取、相关监控和缓存使用开关等。
5.5.5 测试验证
1.单元测试


在测试方法中提供了两套集群的工厂获取及相应方法的使用。通过代理的方式JDKProxyFactory.getProxy(CacheService.class,IIRCacheAdapter.class);获取相应的工厂。这里获取的过程相当于从车间中组合出新的工厂。最终在实际的使用中交给Spring进行Bean注入,通过这样的方式升级服务集群,就不需要所有的研发人员硬编码了。即使有任何问题,也可以回退到原有的实现方式里。这种可插拔服务的优点是易于维护和可扩展。
2.测试结果

从测试结果来看运行正常,升级完成。这种代码的扩展方式远比硬编码 if…else好得多,既可扩展又易维护。研发人员的技术栈、技术经验会决定最终的执行结果,有时候如果具备丰富的技能,即使在非常紧急的情况下,也可以做出非常完善的技术方案和落地结果。