创造者模式:工厂方法、抽象工厂、生成器、原型、单例

这类模式提供创建对象的机制,能够提升已有代码的灵活性和可复用性

第二节、抽象工厂模式

一、抽象工厂模式介绍

抽象工厂图解

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

二、案例场景模拟

模拟redis升级

初期业务的蛮荒发展,会牵动研发对系统的建设

初期预估OPS较低、系统压力较小、并发访问不大、近期改动不大。于是前期可能只需要单机即可满足。
但是随着业务发展,系统的负载能力也要随之跟上,这时候就需要更换为更为健壮的redis集群服务,虽然需要修改但是不能影响目前系统的运行,还要平滑过渡过去。

可以预见的问题会有:

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

1.场景模拟工程

工程项目结构如图所示:

2.场景描述

2.1 模拟单机服务RedisUtils
package org.levtio.demo.design;

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

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

/**
* @Author: LuoMingDong
* @Date: 2023/9/18 10:02
*/
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 String set(String key,String value){
logger.info("Redis写入数据 key:{} val:{}", key, value);
return dataMap.put(key,value);
}
public String set(String key, String value, long timeout, TimeUnit timeUnit){
logger.info("Redis写入数据 key:{} val:{} timeout:{} timeUnit:{}", key, value,timeout,timeUnit);
return dataMap.put(key,value);
}
public void del(String key) {
logger.info("Redis删除数据 key:{}", key);
dataMap.remove(key);
}
}
  1. 模拟Redis功能,也就是假定目前所有系统都在使用的服务。
  2. 类和方法名次都固定写死到各个业务系统中,改动略微麻烦。
2.2 模拟第一个集群EGM
package org.levtio.demo.design.matter;

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

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

/**
* @Author: LuoMingDong
* @Date: 2023/9/18 10:05
*/
public class EGM {
private Logger logger = LoggerFactory.getLogger(EGM.class);
private Map<String,String> dataMap = new ConcurrentHashMap<>();
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);
}
}

模拟一个集群服务,但是方法名有所不同。

2.3 模拟集群 IIR
package org.levtio.demo.design.matter;

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

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

/**
* @Author: LuoMingDong
* @Date: 2023/9/18 10:05
*/
public class IIR {
private Logger logger = LoggerFactory.getLogger(IIR.class);

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

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

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

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

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

这是另外一套集群服务。也有方法名不同

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

3. 单集群代码使用

以下是单集群Redis的使用方式,后续通过对这里的代码进行改造

3.1 定义使用接口
package org.levtio.demo.design;

import java.util.concurrent.TimeUnit;

/**
* @Author: LuoMingDong
* @Date: 2023/9/18 10:05
*/
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);
}
3.2 实现调用接口
package org.levtio.demo.design.cuisine.impl;

import org.levtio.demo.design.CacheService;
import org.levtio.demo.design.RedisUtils;

import java.util.concurrent.TimeUnit;

/**
* @Author: LuoMingDong
* @Date: 2023/9/18 10:06
*/
public class CacheServiceImpl implements CacheService {
private RedisUtils redisUtils = new RedisUtils();
@Override
public String get(String key) {
return redisUtils.get(key);
}

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

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

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

三、使用ifelse代码实现

1. ifelse工程结构

此时只需要两个类,结构简单,需要补充扩展功能在Impl中实现即可。

2. ifelse实现需求

package org.levtio.demo.design.cuisine.impl;

import org.levtio.demo.design.CacheService;
import org.levtio.demo.design.RedisUtils;
import org.levtio.demo.design.matter.EGM;
import org.levtio.demo.design.matter.IIR;

import java.util.concurrent.TimeUnit;

/**
* @Author: LuoMingDong
* @Date: 2023/9/18 11:56
*/
public class CacheServiceImpl implements CacheService {
private RedisUtils redisUtils = new RedisUtils();
private EGM egm = new EGM();
private IIR iir = new IIR();

@Override
public String get(String key, int redisType) {
if (redisType == 1){
return egm.gain(key);
}

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

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

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

@Override
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集群,后期拓展以及维护很麻烦

3. 测试验证

单元测试:

package org.levtio.demo.design.test;

import org.junit.Test;
import org.levtio.demo.design.CacheService;
import org.levtio.demo.design.cuisine.impl.CacheServiceImpl;

/**
* @Author: LuoMingDong
* @Date: 2023/9/18 13:50
*/
public class ApiTest {
@Test
public void test_CacheService() {

CacheService cacheService = new CacheServiceImpl();

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

}
}

测试结果:

13:50:36.508 [main] INFO  org.levtio.demo.design.matter.EGM - EGM写入数据 key:user_name_01 val:levtio
13:50:36.514 [main] INFO org.levtio.demo.design.matter.EGM - EGM获取数据 key:user_name_01
测试结果:levtio

四、使用抽象工厂模式重构代码

1. 抽象工厂模式工程结构

抽象工厂模式结构

如何使用:

CacheService proxy_EGM = JDKProxy.getProxy(CacheServiceImpl.class, new EGMCacheAdapter());

工程中设计的部分核心代码介绍:

  1. ICacheAdapter: 定义了适配接口,分别包装两个集群中差异化的接口名称。EGMCacheAdapter,IIRCacheAdapter.
  2. JDKProxy、JDKInvocationHandler: 是代理类的定义和实现,这部分也就是抽象工厂的另外一种实现方式。通过这样的方式可以把原有操作Redis的方法进行代理操作,通过控制不同的入参对象,控制缓存的使用。

2. 代码实现

2.1 定义适配接口
package org.levtio.demo.design.factory;

import java.util.concurrent.TimeUnit;

/**
* @Author: LuoMingDong
* @Date: 2023/9/18 15:11
*/
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);
}

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

2.2 实现集群使用服务

EGMCacheAdapter

package org.levtio.demo.design.factory.impl;

import org.levtio.demo.design.factory.ICacheAdapter;
import org.levtio.demo.design.matter.EGM;

import java.util.concurrent.TimeUnit;

/**
* @Author: LuoMingDong
* @Date: 2023/9/18 15:21
*/
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);
}
}

IIRCacheAdapter

package org.levtio.demo.design.factory.impl;

import org.levtio.demo.design.factory.ICacheAdapter;
import org.levtio.demo.design.matter.IIR;

import java.util.concurrent.TimeUnit;

/**
* @Author: LuoMingDong
* @Date: 2023/9/18 16:24
*/
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);
}
}

主要操作就是在统一方法名下进行包装

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

JDKProxy

package org.levtio.demo.design.factory;

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

/**
* @Author: LuoMingDong
* @Date: 2023/9/18 16:26
* @Describe: 定义抽象工程代理类和实现
*/
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

package org.levtio.demo.design.factory;

import org.levtio.demo.design.util.ClassLoaderUtils;

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

/**
* @Author: LuoMingDong
* @Date: 2023/9/18 16:35
* @Describe: 抽象工程代理类的实现
*/
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);
}
}
  1. 代理类中的实现,通过穿透进来的集群服务进行方法操作。
  2. 在invoke中通过使用获取方法名称反射方式,调用对应的方法功能,简化了整体的使用。

3. 测试验证

编写测试类

package org.levtio.demo.design;

import org.junit.Test;
import org.levtio.demo.design.factory.JDKProxy;
import org.levtio.demo.design.factory.impl.EGMCacheAdapter;
import org.levtio.demo.design.factory.impl.IIRCacheAdapter;
import org.levtio.demo.design.impl.CacheServiceImpl;

/**
* @Author: LuoMingDong
* @Date: 2023/9/18 16:49
* @Describe:
*/
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", "levtio");
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", "levtio");
String val02 = proxy_IIR.get("user_name_01");
System.out.println("测试结果:" + val02);

}
}

在测试的代码中通过传入不同的集群类型,就可以调用不同的集群下的方法。
代码示例:

JDKProxy.getProxy(CacheServiceImpl.class, new EGMCacheAdapter());

测试结果

09:18:41.437 [main] INFO  org.levtio.demo.design.matter.EGM - EGM写入数据 key:user_name_01 val:levtio
09:18:41.440 [main] INFO org.levtio.demo.design.matter.EGM - EGM获取数据 key:user_name_01
测试结果:levtio
09:18:41.441 [main] INFO org.levtio.demo.design.matter.IIR - IIR写入数据 key:user_name_01 val:levtio
09:18:41.441 [main] INFO org.levtio.demo.design.matter.IIR - IIR获取数据 key:user_name_01
测试结果:levtio

五、总结

  1. 抽象工厂模式,所要解决的问题就是在一个产品族,存在多个不同类型的产品(Redis集群,操作系统)情况下,接口选择的问题。
  2. 这个设计模式满足了:单一职责,开闭原则,解耦等优点。