• Java Design Patterns 之 抽象工厂模式 02


    抽象工厂

    抽象工厂模式与工厂方法模式虽然主要意图都是为了解决接口选择问题,但是在实现上,抽象工厂是一个中心工厂,创建其他工厂的模式。

    案例

    在公司中我们的Redis可能会经历以下的变更:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-og4z3Nuz-1662386062295)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220904220658959.png)]

    最初业务规模可能很小,单机Redis完全足以支持我们的业务,这个单机的Redis目前所有的系统都在用,随着业务规模的扩大,单机Redis不足以支撑我们的业务规模了,必须进行升级,单机 - > 集群 ,而集群环境所提供的API又与单机的服务不同,那么所有的系统都要随之调整

    初级代码实现

    /**
     * Description:缓存服务实现,通过if-else判断走哪个RedisServer
     *
     * @author li.hongjian
     * @email lhj502819@163.com
     * @since 2022/9/4 22:11
     */
    public class CacheServiceImpl implements ICacheService {
    
        /**
         * 单机
         */
        private StandAloneRedis standAloneRedis = new StandAloneRedis();
    
        /**
         * 集群 EGM
         */
        private EGM egm = new EGM();
    
        /**
         * 集群IIR
         */
        private IIR iir = new IIR();
    
    
        @Override
        public String get(String key,int redisServerType) {
            if(redisServerType  == RedisServerTypeEnum.STAND_ALONE.getCode()){
                return standAloneRedis.get(key);
            }else if(redisServerType == RedisServerTypeEnum.EGM.getCode()){
                return egm.gain(key);
            }else {
                return iir.gain(key);
            }
        }
    
        @Override
        public void set(String key, String value, int redisServerType) {
            if(redisServerType  == RedisServerTypeEnum.STAND_ALONE.getCode()){
                standAloneRedis.set(key,value);
            }else if(redisServerType == RedisServerTypeEnum.EGM.getCode()){
                egm.set(key,value);
            }else {
                iir.set(key, value);
            }
        }
    
        @Override
        public void set(String key, String value, long timeout, TimeUnit timeUnit, int redisServerType) {
            if(redisServerType  == RedisServerTypeEnum.STAND_ALONE.getCode()){
                standAloneRedis.set(key,value,timeout,timeUnit);
            }else if(redisServerType == RedisServerTypeEnum.EGM.getCode()){
                egm.setEx(key,value,timeout,timeUnit);
            }else {
                iir.setExpire(key,value,timeout,timeUnit);
            }
        }
    
        @Override
        public void del(String key, int redisServerType) {
            if(redisServerType  == RedisServerTypeEnum.STAND_ALONE.getCode()){
                standAloneRedis.del(key);
            }else if(redisServerType == RedisServerTypeEnum.EGM.getCode()){
                egm.delete(key);
            }else {
                iir.del(key);
            }
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    在这里插入图片描述

    缺点

    每次新增类型都要在ICacheService中新增适配逻辑,不满足开闭原则

    设计模式实现

    设计思想

    通过适配器对不同的Redis集群API进行统一。

    实现

    //定义适配接口,用于包装两个集群中差异化的接口名,让所有的集群提供方能够在统一的方法名称下进行操作,同时方便扩展
    public interface ICacheAdapter extends ICacheService {
    
    }
    
    • 1
    • 2
    • 3
    • 4
    // 对 EGM Redis集群API进行适配
    public class EGMCacheAdapter implements ICacheAdapter {
    
        private EGM egm = new EGM();
    
        @Override
        public String get(String key) {
            return egm.gain(key);
        }
    
        @Override
        public void set(String key, String value) {
            egm.set(key, value);
        }
    
        @Override
        public void set(String key, String value, long timeout, TimeUnit timeUnit) {
            egm.setEx(key, value, timeout, timeUnit);
        }
    
        @Override
        public void del(String key) {
            egm.delete(key);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    //对 IIR Redis集群API进行适配
    public class IIRCacheAdapter implements ICacheAdapter {
    
        private IIR iir = new IIR();
    
        @Override
        public String get(String key) {
            return iir.gain(key);
        }
    
        @Override
        public void set(String key, String value) {
            iir.set(key, value);
        }
    
        @Override
        public void set(String key, String value, long timeout, TimeUnit timeUnit) {
            iir.setExpire(key, value, timeout, timeUnit);
        }
    
        @Override
        public void del(String key) {
            iir.del(key);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    public class JdkInvocationHandler implements InvocationHandler {
    
        private ICacheAdapter cacheAdapter;
    
        /**
         * @param cacheAdapter 具体要执行的RedisServer适配器
         */
        public JdkInvocationHandler(ICacheAdapter cacheAdapter) {
            this.cacheAdapter = cacheAdapter;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //注意 :在执行方法时,执行的是传入的具体的 RedisServer  的方法
            return ICacheAdapter.class.getMethod(method.getName(), ClassLoaderUtils.getClazzByArgs(args)).invoke(cacheAdapter, args);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    //通过代理的方式,每次调用时传入具体要的使用的RedisServer API
    public class JdkProxy {
    
        /**
         * 实现代理类,对于使用哪个集群由外部通过入参传入
         *
         * @param interfaceClass 缓存服务,提供API
         * @param cacheAdapter 具体执行的adapter,如EGMCacheAdapter、IIRCacheAdapter
         */
        public static  T getProxy(Class interfaceClass, ICacheAdapter cacheAdapter) throws Exception{
            InvocationHandler handler = new JdkInvocationHandler(cacheAdapter);
            ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            Class[] classes = interfaceClass.getInterfaces();
            return (T) Proxy.newProxyInstance(classLoader, new Class[]{classes[0]}, handler);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    调用

    public class App {
    
        @Test
        public void test_cacheService(){
            try {
                //通过传入不同的集群类型,就可以调用不同的集群下的API
                //如果后续有扩展的需求,也可以按照这样的类型方式进行补充,同时对于改造上来说并没有改动原来的方法,降低了修改成本
                ICacheService proxyEGM = JdkProxy.getProxy(CacheServiceImpl.class, new EGMCacheAdapter());
                proxyEGM.set("name" , "壹玖");
                String nameEGM = proxyEGM.get("name");
                log.info("获取到name:{} \n" ,nameEGM);
    
                ICacheService proxyIIR = JdkProxy.getProxy(CacheServiceImpl.class, new IIRCacheAdapter());
                proxyIIR.set("name" , "壹玖");
                String nameIIR = proxyIIR.get("name");
                log.info("获取到name:{} \n" ,nameIIR);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    在这里插入图片描述

    缺点

    和 工厂方法一样,随着业务的不断扩展,可能会造成类实现上的复杂度。但也可以说算不上缺点,因为可以随着其他设计方式的引入和代理类以及自动生成加载的方式降低此缺点。

    借鉴

    • 《重学Java设计模式》-小傅哥
  • 相关阅读:
    如何动态的测试Thrift服务
    测试人如何打造简历化思维?
    Java系列之:认识和使用jshell
    cmdb运维管理平台在哪能看
    地图选择器datav怎么使用?
    Centos7系统下挂载超过2TB的目录
    剑指 Offer 15. 二进制中1的个数,位运算,与运算
    手把手教你搭建android模块化项目框架(十二)——实现自定义view的一些小技巧~
    临界区(critical section 每个线程中访问 临界资源 的那段代码)和互斥锁(mutex)的区别(进程间互斥量、共享内存、虚拟地址)
    Flink CDC详解
  • 原文地址:https://blog.csdn.net/qq_43295093/article/details/126714388