设计模式之抽象工厂模式(替换Redis双集群升级,代理类抽象场景)

前言:

看了很多书、学了很多知识,多线程能玩出花,可最后我还是写不好代码!

这就有点像家里装修完了买物件,我几十万的实木沙发,怎么放这里就不好看。同样代码写的不好并不一定是基础技术不足,也不一定是产品要得急 怎么实现我不管明天上线。而很多时候是我们对编码的经验的不足和对架构的把控能力不到位,我相信产品的第一个需求往往都不复杂,甚至所见所得。但如果你不考虑后续的是否会拓展,将来会在哪些模块继续添加功能,那么后续的代码就会随着你种下的第一颗恶性的种子开始蔓延。
抽象工厂模式介绍

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

预估QPS较低、系统压力较小、并发访问不大、近一年没有大动作等等,在考虑时间投入成本的前提前,并不会投入特别多的人力去构建非常完善的系统。就像对 Redis 的使用,往往可能只要是单机的就可以满足现状。

模拟单机服务 RedisUtils

java 复制代码
package com.lm.design;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

public class RedisUtils {

    private Logger logger = LoggerFactory.getLogger(RedisUtils.class);

    private Map<String, String> dataMap = new ConcurrentHashMap<String, String>();

    public String get(String key) {
        logger.info("Redis获取数据 key:{}", key);
        return dataMap.get(key);
    }

    public void set(String key, String value) {
        logger.info("Redis写入数据 key:{} val:{}", key, value);
        dataMap.put(key, value);
    }

    public void set(String key, String value, long timeout, TimeUnit timeUnit) {
        logger.info("Redis写入数据 key:{} val:{} timeout:{} timeUnit:{}", key, value, timeout, timeUnit.toString());
        dataMap.put(key, value);
    }

    public void del(String key) {
        logger.info("Redis删除数据 key:{}", key);
        dataMap.remove(key);
    }

}

模拟Redis功能,也就是假定目前所有的系统都在使用的服务

类和方法名称都固定写死到各个业务系统中,改动略微麻烦
模拟集群 EGM

java 复制代码
package com.lm.design.matter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

public class EGM {

    private Logger logger = LoggerFactory.getLogger(EGM.class);

    private Map<String, String> dataMap = new ConcurrentHashMap<String, String>();

    public String gain(String key) {
        logger.info("EGM获取数据 key:{}", key);
        return dataMap.get(key);
    }

    public void set(String key, String value) {
        logger.info("EGM写入数据 key:{} val:{}", key, value);
        dataMap.put(key, value);
    }

    public void setEx(String key, String value, long timeout, TimeUnit timeUnit) {
        logger.info("EGM写入数据 key:{} val:{} timeout:{} timeUnit:{}", key, value, timeout, timeUnit.toString());
        dataMap.put(key, value);
    }

    public void delete(String key) {
        logger.info("EGM删除数据 key:{}", key);
        dataMap.remove(key);
    }
}

模拟集群 IIR

有时候在企业开发中就很有可能出现两套服务,这里我们也是为了做模拟案例,所以添加两套实现同样功能的不同服务,来学习抽象工厂模式。

目前的系统中已经在大量的使用redis服务,但是因为系统不能满足业务的快速发展,因此需要迁移到集群服务中。而这时有两套集群服务需要兼容使用,又要满足所有的业务系统改造的同时不影响线上使用。

单机群代码使用

java 复制代码
package com.lm.design;

import java.util.concurrent.TimeUnit;

public interface CacheService {

    String get(final String key);

    void set(String key, String value);

    void set(String key, String value, long timeout, TimeUnit timeUnit);

    void del(String key);

}

使用代码调用

java 复制代码
package com.lm.design.cuisine.impl;

import com.lm.design.CacheService;
import com.lm.design.RedisUtils;

import java.util.concurrent.TimeUnit;

public class CacheServiceImpl implements CacheService {

    private RedisUtils redisUtils = new RedisUtils();

    public String get(String key) {
        return redisUtils.get(key);
    }

    public void set(String key, String value) {
        redisUtils.set(key, value);
    }

    public void set(String key, String value, long timeout, TimeUnit timeUnit) {
        redisUtils.set(key, value, timeout, timeUnit);
    }

    public void del(String key) {
        redisUtils.del(key);
    }

}

目前的代码对于当前场景下的使用没有什么问题,也比较简单。但是所有的业务系统都在使用同时,需要改造就不那么容易了
ifelse实现需求

java 复制代码
package com.lm.design.cuisine.impl;

import com.lm.design.CacheService;
import com.lm.design.RedisUtils;
import com.lm.design.matter.EGM;
import com.lm.design.matter.IIR;

import java.util.concurrent.TimeUnit;

public class CacheServiceImpl implements CacheService {

    private RedisUtils redisUtils = new RedisUtils();

    private EGM egm = new EGM();

    private IIR iir = new IIR();

    public String get(String key, int redisType) {

        if (1 == redisType) {
            return egm.gain(key);
        }

        if (2 == redisType) {
            return iir.get(key);
        }

        return redisUtils.get(key);
    }

    public void set(String key, String value, int redisType) {

        if (1 == redisType) {
            egm.set(key, value);
            return;
        }

        if (2 == redisType) {
            iir.set(key, value);
            return;
        }

        redisUtils.set(key, value);
    }

    public void set(String key, String value, long timeout, TimeUnit timeUnit, int redisType) {

        if (1 == redisType) {
            egm.setEx(key, value, timeout, timeUnit);
            return;
        }

        if (2 == redisType) {
            iir.setExpire(key, value, timeout, timeUnit);
            return;
        }

        redisUtils.set(key, value, timeout, timeUnit);
    }

    public void del(String key, int redisType) {

        if (1 == redisType) {
            egm.delete(key);
            return;
        }

        if (2 == redisType) {
            iir.del(key);
            return;
        }

        redisUtils.del(key);
    }
}

这里的实现过程非常简单,主要根据类型判断是哪个Redis集群。

虽然实现是简单了,但是对使用者来说就麻烦了,并且也很难应对后期的拓展和不停的维护。
测试验证

java 复制代码
package com.lm.design.test;

import com.lm.design.CacheService;
import com.lm.design.cuisine.impl.CacheServiceImpl;
import org.junit.Test;

public class ApiTest {

    @Test
    public void test_CacheService() {

        CacheService cacheService = new CacheServiceImpl();

        cacheService.set("user_name_01", "小明哥", 1);
        String val01 = cacheService.get("user_name_01", 1);
        System.out.println("测试结果:" + val01);

    }

}


抽象工厂模式重构代码

这里的抽象工厂的创建和获取方式,会采用代理类的方式进行实现。所被代理的类就是目前的Redis操作方法类,让这个类在不需要任何修改下,就可以实现调用集群A和集群B的数据服务。

并且这里还有一点非常重要,由于集群A和集群B在部分方法提供上是不同的,因此需要做一个接口适配,而这个适配类就相当于工厂中的工厂,用于创建把不同的服务抽象为统一的接口做相同的业务。

ICacheAdapter,定义了适配接口,分别包装两个集群中差异化的接口名称。EGMCacheAdapter、IIRCacheAdapter

JDKProxy、JDKInvocationHandler,是代理类的定义和实现,这部分也就是抽象工厂的另外一种实现方式。通过这样的方式可以很好的把原有操作Redis的方法进行代理操作,通过控制不同的入参对象,控制缓存的使用。

定义适配接口

java 复制代码
package com.lm.desgin.factory;

import java.util.concurrent.TimeUnit;

public interface ICacheAdapter {

    String get(String key);

    void set(String key, String value);

    void set(String key, String value, long timeout, TimeUnit timeUnit);

    void del(String key);

}

这个类的主要作用是让所有集群的提供方,能在统一的方法名称下进行操作。也方面后续的拓展。
实现集群使用服务

java 复制代码
package com.lm.desgin.factory.impl;

import com.lm.desgin.factory.ICacheAdapter;
import com.lm.design.matter.EGM;

import java.util.concurrent.TimeUnit;

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);
    }
}
java 复制代码
package com.lm.desgin.factory.impl;

import com.lm.desgin.factory.ICacheAdapter;
import com.lm.design.matter.IIR;

import java.util.concurrent.TimeUnit;

public class IIRCacheAdapter implements ICacheAdapter {

    private IIR iir = new IIR();

    @Override
    public String get(String key) {
        return iir.get(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);
    }

}

以上两个实现都非常容易,在统一方法名下进行包装。

定义抽象工程代理类和实现

java 复制代码
package com.lm.desgin.factory;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class JDKProxy {

    public static <T> T getProxy(Class<T> 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);
    }

}

这里主要的作用就是完成代理类,同时对于使用哪个集群由外部通过入参进行传递。

java 复制代码
package com.lm.desgin.factory;

import com.lm.desgin.util.ClassLoaderUtils;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class JDKInvocationHandler implements InvocationHandler {

    private ICacheAdapter cacheAdapter;

    public JDKInvocationHandler(ICacheAdapter cacheAdapter) {
        this.cacheAdapter = cacheAdapter;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return ICacheAdapter.class.getMethod(method.getName(), ClassLoaderUtils.getClazzByArgs(args)).invoke(cacheAdapter, args);
    }

}

A:在代理类的实现中其实也非常简单,通过穿透进来的集群服务进行方法操作另外在invoke中通过使B:用获取方法名称反射方式,调用对应的方法功能,也就简化了整体的使用。
测试验证

java 复制代码
package com.lm.desgin.test;

import com.lm.desgin.CacheService;
import com.lm.desgin.impl.CacheServiceImpl;
import com.lm.desgin.factory.JDKProxy;
import com.lm.desgin.factory.impl.EGMCacheAdapter;
import com.lm.desgin.factory.impl.IIRCacheAdapter;
import org.junit.Test;

public class ApiTest {

    @Test
    public void test_CacheService() throws Exception {

        CacheService proxy_EGM = JDKProxy.getProxy(CacheServiceImpl.class, new EGMCacheAdapter());
        proxy_EGM.set("user_name_01", "小明哥");
        String val01 = proxy_EGM.get("user_name_01");
        System.out.println("测试结果:" + val01);

        CacheService proxy_IIR = JDKProxy.getProxy(CacheServiceImpl.class, new IIRCacheAdapter());
        proxy_IIR.set("user_name_01", "小明哥");
        String val02 = proxy_IIR.get("user_name_01");
        System.out.println("测试结果:" + val02);

    }

}

研发自我能力的提升远不是外接的压力就是编写一坨坨代码的接口,如果你已经熟练了很多技能,那么可以在即使紧急的情况下,也能做出完善的方案。
总结

抽象工厂模式,所要解决的问题就是在一个产品族,存在多个不同类型的产品(Redis集群、操作系统)情况下,接口选择的问题。而这种场景在业务开发中也是非常多见的,只不过可能有时候没有将它们抽象化出来。

那么这个设计模式满足了;单一职责、开闭原则、解耦等优点,但如果说随着业务的不断拓展,可能会造成类实现上的复杂度。但也可以说算不上缺点,因为可以随着其他设计方式的引入和代理类以及自动生成加载的方式降低此项缺点。

好了 至此设计模式之抽象工厂模式(替换Redis双集群升级,代理类抽象场景) 学习结束了 友友们 点点关注不迷路 老铁们!!!!!

相关推荐
FIN技术铺2 小时前
Redis集群模式之Redis Sentinel vs. Redis Cluster
数据库·redis·sentinel
程序员曦曦6 小时前
一文熟悉redis安装和字符串基本操作
自动化测试·软件测试·数据库·redis·功能测试·程序人生·缓存
风再云巅7 小时前
Redis下载历史版本
redis
Java 第一深情8 小时前
Redis经典面试题-深度剖析
数据库·redis·缓存
瞎姬霸爱.9 小时前
设计模式-七个基本原则之一-里氏替换原则
java·设计模式·里氏替换原则
monkey_meng10 小时前
【Rust设计模式之建造者模式】
后端·设计模式·rust·建造者模式
蜜獾云10 小时前
redis 三种持久化对比
数据库·redis·缓存
ktkiko1112 小时前
Redis中的数据结构
数据结构·数据库·redis
basic_code14 小时前
Docker使用docker-compose一键部署nacos、Mysql、redis
运维·redis·mysql·docker·nacos
sunyanchun16 小时前
如何保证Redis与MySQL双写一致性
redis·mysql