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

抽象⼯⼚模式

抽象⼯⼚模式

1.抽象工厂模式介绍

image-20230101170521377

抽象⼯⼚模式与⼯⼚⽅法模式虽然主要意图都是为了解决,接⼝选择问题。但在实现上,抽象⼯⼚是⼀

个中⼼⼯⼚,创建其他⼯⼚的模式。

2.案例场景模拟

2.1场景简述

image-20230101170732511

很多时候初期业务的蛮荒发展,也会牵动着研发对系统的建设。

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

但随着业务超过预期的快速发展,系统的负载能力也要随着跟上。原有的单机 Redis 已经满足不了系统需求。这时候就需要更换为更为健壮的Redis集群服务,虽然需要修改但是不能影响目前系统的运行,还要平滑过渡过去。

随着这次的升级,可以预见的问题会有;

  1. 很多服务用到了Redis需要一起升级到集群。
  2. 需要兼容集群A和集群B,便于后续的灾备。
  3. 两套集群提供的接口和方法各有差异,需要做适配。
  4. 不能影响到目前正常运行的系统。

模拟单机服务 RedisUtils

image-20230101171054847

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

类和⽅法名次都固定写死到各个业务系统中,改动略微麻烦

模拟集群 EGM

image-20230101171114191

  • 模拟一个集群服务,但是方法名与各业务系统中使用的方法名不同。有点像你mac,我用win。做一样的事,但有不同的操作。

模拟集群 IIR

image-20230101171219167

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

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

2.2单集群代码使用

以下是案例模拟中原有的单集群Redis使用方式,后续会通过对这里的代码进行改造。

image-20230101171500847

定义使用接口

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

}
 

实现调用代码

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

}
  • 目前的代码对于当前场景下的使用没有什么问题。但是所有的业务系统都在使用同时,需要改造就不那么容易了

3.用一坨坨代码实现

此时的实现方式并不会修改类结构图,也就是与上面给出的类层级关系一致。通过在接口中添加类型字段区分当前使用的是哪个集群,来作为使用的判断。可以说目前的方式非常难用,其他使用方改动颇多,这里只是做为例子。

ifelse实现需求

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

    //... 

}
 
  • 这里的实现过程非常简单,主要根据类型判断是哪个Redis集群。
  • 虽然实现是简单了,但是对使用者来说就麻烦了,并且也很难应对后期的拓展和不停的维护。

测试验证

编写测试类

@Test
public void test_CacheService() {
    CacheService cacheService = new CacheServiceImpl();
    cacheService.set("user_name_01", "llp", 1);
    String val01 = cacheService.get("user_name_01",1);
    System.out.println(val01);
}
 

结果

22:26:24.591 [main] INFO  org.itstack.demo.design.matter.EGM - EGM写入数据 key:user_name_01 val:llp
22:26:24.593 [main] INFO  org.itstack.demo.design.matter.EGM - EGM获取数据 key:user_name_01
测试结果:llp

Process finished with exit code 0

4.抽象工厂模式重构代码

抽象⼯⼚模型结构

image-20230101172146075

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

  • 工程中涉及的部分核心功能代码,如下;
    • ICacheAdapter,定义了适配接口,分别包装两个集群中差异化的接口名称。EGMCacheAdapterIIRCacheAdapter
    • JDKProxyJDKInvocationHandler,是代理类的定义和实现,这部分也就是抽象工厂的另外一种实现方式。通过这样的方式可以很好的把原有操作Redis的方法进行代理操作,通过控制不同的入参对象,控制缓存的使用。

代码实现

定义适配接口

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

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

实现集群使用服务

EGMCacheAdapter

public class EGMCacheAdapter implements ICacheAdapter {

    private EGM egm = new EGM();

    public String get(String key) {
        return egm.gain(key);
    }

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

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

    public void del(String key) {
        egm.delete(key);
    }
}

IIRCacheAdapter

public class IIRCacheAdapter implements ICacheAdapter {

    private IIR iir = new IIR();

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

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

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

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

}
 

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

JDKProxy

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

}

JDKInvocationHandler

public class JDKInvocationHandler implements InvocationHandler {

    private ICacheAdapter cacheAdapter;

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

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

}

ClassLoaderUtils工具类

public class ClassLoaderUtils {

    private static Set<Class> primitiveSet = new HashSet<Class>();

    static {
        primitiveSet.add(Integer.class);
        primitiveSet.add(Long.class);
        primitiveSet.add(Float.class);
        primitiveSet.add(Byte.class);
        primitiveSet.add(Short.class);
        primitiveSet.add(Double.class);
        primitiveSet.add(Character.class);
        primitiveSet.add(Boolean.class);
    }

    /**
     * 得到当前ClassLoader
     *
     * @return ClassLoader
     */
    public static ClassLoader getCurrentClassLoader() {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        if (cl == null) {
            cl = ClassLoaderUtils.class.getClassLoader();
        }
        return cl == null ? ClassLoader.getSystemClassLoader() : cl;
    }

    /**
     * 得到当前ClassLoader
     *
     * @param clazz 某个类
     * @return ClassLoader
     */
    public static ClassLoader getClassLoader(Class<?> clazz) {
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        if (loader != null) {
            return loader;
        }
        if (clazz != null) {
            loader = clazz.getClassLoader();
            if (loader != null) {
                return loader;
            }
            return clazz.getClassLoader();
        }
        return ClassLoader.getSystemClassLoader();
    }

    /**
     * 根据类名加载Class
     *
     * @param className 类名
     * @return Class
     * @throws ClassNotFoundException 找不到类
     */
    public static Class forName(String className)
            throws ClassNotFoundException {
        return forName(className, true);
    }

    /**
     * 根据类名加载Class
     *
     * @param className  类名
     * @param initialize 是否初始化
     * @return Class
     * @throws ClassNotFoundException 找不到类
     */
    public static Class forName(String className, boolean initialize)
            throws ClassNotFoundException {
        return Class.forName(className, initialize, getCurrentClassLoader());
    }

    /**
     * 根据类名加载Class
     *
     * @param className 类名
     * @param cl        Classloader
     * @return Class
     * @throws ClassNotFoundException 找不到类
     */
    public static Class forName(String className, ClassLoader cl)
            throws ClassNotFoundException {
        return Class.forName(className, true, cl);
    }

    /**
     * 实例化一个对象(只检测默认构造函数,其它不管)
     *
     * @param clazz 对象类
     * @param <T>   对象具体类
     * @return 对象实例
     * @throws Exception 没有找到方法,或者无法处理,或者初始化方法异常等
     */
    public static <T> T newInstance(Class<T> clazz) throws Exception {
        if (primitiveSet.contains(clazz)) {
            return null;
        }
        if (clazz.isMemberClass() && !Modifier.isStatic(clazz.getModifiers())) {
            Constructor constructorList[] = clazz.getDeclaredConstructors();
            Constructor defaultConstructor = null;
            for (Constructor con : constructorList) {
                if (con.getParameterTypes().length == 1) {
                    defaultConstructor = con;
                    break;
                }
            }
            if (defaultConstructor != null) {
                if (defaultConstructor.isAccessible()) {
                    return (T) defaultConstructor.newInstance(new Object[]{null});
                } else {
                    try {
                        defaultConstructor.setAccessible(true);
                        return (T) defaultConstructor.newInstance(new Object[]{null});
                    } finally {
                        defaultConstructor.setAccessible(false);
                    }
                }
            } else {
                throw new Exception("The " + clazz.getCanonicalName() + " has no default constructor!");
            }
        }
        try {
            return clazz.newInstance();
        } catch (Exception e) {
            Constructor<T> constructor = clazz.getDeclaredConstructor();
            if (constructor.isAccessible()) {
                throw new Exception("The " + clazz.getCanonicalName() + " has no default constructor!", e);
            } else {
                try {
                    constructor.setAccessible(true);
                    return constructor.newInstance();
                } finally {
                    constructor.setAccessible(false);
                }
            }
        }
    }

    public static Class<?>[] getClazzByArgs(Object[] args) {
        Class<?>[] parameterTypes = new Class[args.length];
        for (int i = 0; i < args.length; i++) {
            if (args[i] instanceof ArrayList) {
                parameterTypes[i] = List.class;
                continue;
            }
            if (args[i] instanceof LinkedList) {
                parameterTypes[i] = List.class;
                continue;
            }
            if (args[i] instanceof HashMap) {
                parameterTypes[i] = Map.class;
                continue;
            }
            if (args[i] instanceof Long){
                parameterTypes[i] = long.class;
                continue;
            }
            if (args[i] instanceof Double){
                parameterTypes[i] = double.class;
                continue;
            }
            if (args[i] instanceof TimeUnit){
                parameterTypes[i] = TimeUnit.class;
                continue;
            }
            parameterTypes[i] = args[i].getClass();
        }
        return parameterTypes;
    }

    public Method getMethod(Class<?> classType, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {
        return classType.getMethod(methodName, parameterTypes);
    }

}

测试验证

@Test
public void test_CacheService() throws Exception {
    CacheService proxy_EGM = JDKProxy.getProxy(CacheServiceImpl.class, new EGMCacheAdapter());
    proxy_EGM.set("user_name_01","llp");
    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","llp");
    String val02 = proxy_IIR.get("user_name_01");
    System.out.println(val02);
}
23:07:06.953 [main] INFO  org.itstack.demo.design.matter.EGM - EGM写入数据 key:user_name_01 val:llp
23:07:06.956 [main] INFO  org.itstack.demo.design.matter.EGM - EGM获取数据 key:user_name_01
测试结果:llp
23:07:06.957 [main] INFO  org.itstack.demo.design.matter.IIR - IIR写入数据 key:user_name_01 val:llp
23:07:06.957 [main] INFO  org.itstack.demo.design.matter.IIR - IIR获取数据 key:user_name_01
测试结果:llp

Process finished with exit code 0

5.总结

  • 抽象工厂模式,所要解决的问题就是在一个产品族,存在多个不同类型的产品(Redis集群、操作系统)情况下,接口选择的问题。而这种场景在业务开发中也是非常多见的,只不过可能有时候没有将它们抽象化出来。
  • 你的代码只是被ifelse埋上了!当你知道什么场景下何时可以被抽象工程优化代码,那么你的代码层级结构以及满足业务需求上,都可以得到很好的完成功能实现并提升扩展性和优雅度。
  • 那么这个设计模式满足了;单一职责、开闭原则、解耦等优点,但如果说随着业务的不断拓展,可能会造成类实现上的复杂度。但也可以说算不上缺点,因为可以随着其他设计方式的引入和代理类以及自动生成加载的方式降低此项缺点。

相关文章:

  • 基于React Native开发的非法App破解记录
  • 年度征文 | 回顾2022,展望2023(我难忘的2022,我憧憬的2023)
  • JavaScript篇.day08-DOM,节点,事件,定时器,位置及坐标
  • QML教程(七) JavaScript
  • 蓝桥杯寒假集训第四天(全球变暖DFS)
  • VScode中不同目录间python库函数的调用
  • C语言版扫雷——从0到1实现扫雷小游戏
  • 机器学习笔记 - 模式识别之图像特征提取和特征选择的基本方法总结
  • APP应用渗透测试思路
  • 微信小程序框架
  • 网络编程 udp/ip协议 c/s模型
  • 【数据结构】C语言实现链表(单链表部分)
  • JAVA练习8
  • 聊聊最适合程序员的画图工具
  • JAVA数据结构篇--12理解LinkedHashSetTreeSet
  • Android 架构优化~MVP 架构改造
  • Android开发 - 掌握ConstraintLayout(四)创建基本约束
  • Angular6错误 Service: No provider for Renderer2
  • canvas绘制圆角头像
  • Druid 在有赞的实践
  • ES6 ...操作符
  • iOS 颜色设置看我就够了
  • k8s如何管理Pod
  • LeetCode18.四数之和 JavaScript
  • maya建模与骨骼动画快速实现人工鱼
  • React16时代,该用什么姿势写 React ?
  • Webpack入门之遇到的那些坑,系列示例Demo
  • 给Prometheus造假数据的方法
  • 聊聊hikari连接池的leakDetectionThreshold
  • 马上搞懂 GeoJSON
  • 爬虫进阶 -- 神级程序员:让你的爬虫就像人类的用户行为!
  • 前端每日实战:70# 视频演示如何用纯 CSS 创作一只徘徊的果冻怪兽
  • 深度学习中的信息论知识详解
  • 使用 5W1H 写出高可读的 Git Commit Message
  • 小程序开发之路(一)
  • 新手搭建网站的主要流程
  • 智能合约开发环境搭建及Hello World合约
  • 阿里云服务器如何修改远程端口?
  • #if #elif #endif
  • (¥1011)-(一千零一拾一元整)输出
  • (03)光刻——半导体电路的绘制
  • (delphi11最新学习资料) Object Pascal 学习笔记---第7章第3节(封装和窗体)
  • (ros//EnvironmentVariables)ros环境变量
  • (分享)自己整理的一些简单awk实用语句
  • (附源码)ssm基于jsp高校选课系统 毕业设计 291627
  • (七)MySQL是如何将LRU链表的使用性能优化到极致的?
  • (十二)python网络爬虫(理论+实战)——实战:使用BeautfulSoup解析baidu热搜新闻数据
  • (一)Linux+Windows下安装ffmpeg
  • (转)利用PHP的debug_backtrace函数,实现PHP文件权限管理、动态加载 【反射】...
  • * CIL library *(* CIL module *) : error LNK2005: _DllMain@12 already defined in mfcs120u.lib(dllmodu
  • **PyTorch月学习计划 - 第一周;第6-7天: 自动梯度(Autograd)**
  • .h头文件 .lib动态链接库文件 .dll 动态链接库
  • .NET Core工程编译事件$(TargetDir)变量为空引发的思考
  • .NET Core引入性能分析引导优化
  • .net 后台导出excel ,word