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

ops/2024/11/14 5:24:06/

前言:
看了很多书、学了很多知识,多线程能玩出花,可最后我还是写不好代码!
这就有点像家里装修完了买物件,我几十万的实木沙发,怎么放这里就不好看。同样代码写的不好并不一定是基础技术不足,也不一定是产品要得急 怎么实现我不管明天上线。而很多时候是我们对编码的经验的不足和对架构的把控能力不到位,我相信产品的第一个需求往往都不复杂,甚至所见所得。但如果你不考虑后续的是否会拓展,将来会在哪些模块继续添加功能,那么后续的代码就会随着你种下的第一颗恶性的种子开始蔓延。
抽象工厂模式介绍
在这里插入图片描述
抽象工厂模式与工厂方法模式虽然主要意图都是为了解决,接口选择问题。但在实现上,抽象工厂是一个中心工厂,创建其他工厂的模式。
案例场景模拟
预估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 {@Testpublic 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();@Overridepublic String get(String key) {return egm.gain(key);}@Overridepublic void set(String key, String value) {egm.set(key, value);}@Overridepublic void set(String key, String value, long timeout, TimeUnit timeUnit) {egm.setEx(key, value, timeout, timeUnit);}@Overridepublic 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();@Overridepublic String get(String key) {return iir.get(key);}@Overridepublic void set(String key, String value) {iir.set(key, value);}@Overridepublic void set(String key, String value, long timeout, TimeUnit timeUnit) {iir.setExpire(key, value, timeout, timeUnit);}@Overridepublic 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;}@Overridepublic 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 {@Testpublic 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.ppmy.cn/ops/132466.html

相关文章

VMware 虚拟机使用教程及 Kali Linux 安装指南

VMware 虚拟机使用教程及 Kali Linux 安装指南 在现代计算机科学与网络安全领域&#xff0c;虚拟化技术的应用越来越广泛。VMware 是一款功能强大的虚拟化软件&#xff0c;可以帮助用户在同一台物理机上运行多个操作系统。本文将详细介绍如何使用 VMware 虚拟机&#xff0c;并…

Sql面试题(一)求排名top10

需求&#xff1a; 1&#xff09;表名&#xff1a;t_patent_detail &#xff08;专利明细表&#xff09; 2&#xff09;表字段&#xff1a;专利号(patent_id)、专利名称(patent_name)、专利类型(patent_type)、申请时间 (aplly_date)、授权时间(authorize_date)、申请人(appl…

Linux(CentOS)设置防火墙开放8080端口,运行jar包,接收请求

1、查看防火墙状态 systemctl status firewalld 防火墙开启状态 2、运行 jar 包&#xff0c;使用8080端口 程序正常启动 3、使用 postman 发送请求&#xff0c;失败 4、检查端口是否开放&#xff08;需更换到 root 用户&#xff09; firewall-cmd --zonepublic --query-por…

QT中 update()函数无法实时调用 paintEvent

QT中 update()函数无法实时调用 paintEvent&#xff01; 在QT中&#xff0c;update()函数用于标记一个窗口区域为“需要重绘”。当调用update()后&#xff0c;QT会在合适的时候调用paintEvent()来重绘这个区域。然而&#xff0c;update()不会立即调用paintEvent()&#xff0c;…

C# NUnit 框架:高效使用指南

一、NUnit 简介 NUnit 是一个专门为.NET 语言设计的开源单元测试框架&#xff0c;它基于 xUnit 架构&#xff0c;提供了丰富的断言方法和测试运行机制&#xff0c;帮助开发者轻松地编写和执行单元测试用例。使用 NUnit&#xff0c;我们可以对代码中的各个功能单元进行独立测试&…

汽车和飞机研制过程中“骡车”和“铁鸟”

在汽车和飞机的研制过程中&#xff0c;“骡车”和“铁鸟”都扮演着至关重要的角色。 “骡车”在汽车研制中&#xff0c;是一种处于原型车和量产车之间的过渡阶段产物。它通常由不同的零部件组合而成&#xff0c;就像骡子是马和驴的杂交后代一样&#xff0c;取各家之长。“骡车…

斯坦福医学部发布GPT润色本子教程

最近&#xff0c;斯坦福大学医学部在GitHub上发布了一份针对申请资源本子润色的详细指导&#xff0c;包括使用GPT和其他大型语言模型来提升学术写作质量的全面建议。本文将为大家梳理这些润色指令&#xff0c;帮助你更好地理解和利用AI工具来优化学术写作。 指令集合 1. 提升文…

Cent OS-7的Apache服务配置

WWW是什么&#xff1f; WWW&#xff08;World Wide Web&#xff0c;万维网&#xff09;是一个全球性的信息空间&#xff0c;其中的文档和其他资源通过URL标识&#xff0c;并通过HTTP或其他协议访问。万维网是互联网的一个重要组成部分&#xff0c;但它并不是互联网的全部。互联…