当前位置: 首页 > article >正文

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

前言:
看了很多书、学了很多知识,多线程能玩出花,可最后我还是写不好代码!
这就有点像家里装修完了买物件,我几十万的实木沙发,怎么放这里就不好看。同样代码写的不好并不一定是基础技术不足,也不一定是产品要得急 怎么实现我不管明天上线。而很多时候是我们对编码的经验的不足和对架构的把控能力不到位,我相信产品的第一个需求往往都不复杂,甚至所见所得。但如果你不考虑后续的是否会拓展,将来会在哪些模块继续添加功能,那么后续的代码就会随着你种下的第一颗恶性的种子开始蔓延。
抽象工厂模式介绍
在这里插入图片描述
抽象工厂模式与工厂方法模式虽然主要意图都是为了解决,接口选择问题。但在实现上,抽象工厂是一个中心工厂,创建其他工厂的模式。
案例场景模拟
预估QPS较低、系统压力较小、并发访问不大、近一年没有大动作等等,在考虑时间投入成本的前提前,并不会投入特别多的人力去构建非常完善的系统。就像对 Redis 的使用,往往可能只要是单机的就可以满足现状。
在这里插入图片描述
模拟单机服务 RedisUtils

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

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服务,但是因为系统不能满足业务的快速发展,因此需要迁移到集群服务中。而这时有两套集群服务需要兼容使用,又要满足所有的业务系统改造的同时不影响线上使用。

单机群代码使用

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);

}

使用代码调用

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实现需求

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

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的方法进行代理操作,通过控制不同的入参对象,控制缓存的使用。

定义适配接口

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);

}

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

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);
    }
}

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);
    }

}

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

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

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);
    }

}

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

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:用获取方法名称反射方式,调用对应的方法功能,也就简化了整体的使用。
测试验证

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双集群升级,代理类抽象场景) 学习结束了 友友们 点点关注不迷路 老铁们!!!!!


http://www.kler.cn/a/391847.html

相关文章:

  • 时间管理的三个痛点
  • win32 / WTL 开发多线程应用,子线程传递大对象给UI线程(主窗口)的方法
  • GISBox VS ArcGIS:分别适用于大型和小型项目的两款GIS软件
  • Cesium加载大量点数据卡顿处理办法
  • 【leetcode练习·二叉树】用「分解问题」思维解题 II
  • linux安装netstat命令
  • 常用中间件介绍
  • Linux(CentOS)开放端口/关闭端口
  • Windows10下局域网的两台电脑间传输文件
  • 2024年9月青少年软件编程(C语言/C++)等级考试试卷(七级)
  • MTSET可溶于DMSO、DMF、THF等有机溶剂,并在水中有轻微的溶解性,91774-25-3
  • AutoDL使用经验
  • vue3使用element-plus,树组件el-tree增加引导线
  • 基于交互多模型 (IMM) 算法的目标跟踪,使用了三种运动模型:匀速运动 (CV)、匀加速运动 (CA) 和匀转弯运动 (CT)。滤波方法为EKF
  • Windows下使用adb实现在模拟器中ping
  • AI制作表情包,每月躺赚1W+,完整流程制作多重变现教学
  • 通过pin_memory 优化 PyTorch 数据加载和传输:工作原理、使用场景与性能分析
  • 探索MoviePy:Python视频编辑的瑞士军刀
  • C/C++每日一练:编写一个查找子串的位置函数
  • PyQt5 加载UI界面与资源文件
  • django博客项目实现站内搜索功能
  • Could not initialize class sun.awt.X11FontManager
  • React Hooks在现代前端开发中的应用
  • vue3+ant design vue实现表单模糊查询
  • 移动硬盘需要格式化才能打开?详解原因与数据恢复方案
  • C++函数传递引用或指针