Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

抽象工厂模式介绍

抽象工厂也可以称作其他工厂的工厂,它可以在抽象工厂中创建其他工厂,与工厂模式一样,都是用来解决选择的问题,同样属于创建型模式。

抽象工厂模式简单使用

这边有一个使用场景:一个系统开始使用时体量比较小,使用的是redis单机模式,但是经过一段时间的发展需要将redis单机扩展为集群模式,切为了灾备,准备了集群A和集群B,这样的场景该怎么处理呢。

我们定义一个RedisUtils来模拟单机服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.bestrookie.redis;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author bestrookie
* @date 2021/10/28 11:04 上午
*/
@Slf4j
public class RedisUtils {
private Map<String,String> dataMap = new ConcurrentHashMap<String, String>();
public String get(String key){
log.info("redis获取数据 key: "+key);
return dataMap.get(key);
}
public void set(String key,String value){
log.info("redis写入数据 key: {} val: {}",key,value);
dataMap.put(key,value);
}
public void del(String key){
log.info("redis删除数据 val: {}",key);
dataMap.remove(key);
}
}

Redis集群服务EGM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.bestrookie.redis.cluster;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author bestrookie
* @date 2021/10/28 11:15 上午
*/
@Slf4j
public class EGM {
private Map<String, String> dataMap = new ConcurrentHashMap<String, String>();
public String gain(String key){
log.info("EGM获取数据 key: {}",key);
return dataMap.get(key);
}
public void set(String key, String value){
log.info("EGM写入数据 key: {} val: {}",key,value);
dataMap.put(key, value);
}
public void delete(String key){
log.info("EGM删除数据 key: {}",key);
dataMap.remove(key);
}
}

Redis集群服务IIR

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.bestrookie.redis.cluster;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author bestrookie
* @date 2021/10/28 11:22 上午
*/
@Slf4j
public class IIR {
private Map<String, String> dataMap = new ConcurrentHashMap<String, String>();
public String getData(String key){
log.info("IIR获取数据 key: {}",key);
return dataMap.get(key);
}
public void setData(String key,String value){
log.info("IIR写入数据 key: {} val: {}",key,value);
dataMap.put(key, value);
}
public void delData(String key){
log.info("IIR删除数据 key: {}",key);
dataMap.remove(key);
}
}

模拟早期单体Redis使用

1、定义Redis使用接口

1
2
3
4
5
6
7
8
9
10
package com.bestrookie.service;
/**
* @author bestrookie
* @date 2021/10/28 11:29 上午
*/
public interface CacheService {
String get(String key);
void set(String key,String value);
void del(String key);
}

2、实现Redis使用接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.bestrookie.service;
import com.bestrookie.redis.RedisUtils;
import com.bestrookie.redis.cluster.EGM;
import com.bestrookie.redis.cluster.IIR;
/**
* @author bestrookie
* @date 2021/10/28 11:32 上午
*/
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 del(String key) {
redisUtils.del(key);
}
}

违背设计模式的实现

如果不是从全局升级改造考虑,仅仅是升级自己的系统,那么直接添加if…else那是最快的,但是使用这样方式添加进去,原先使用这个接口的代码,都需要改动

if…else实现升级

接口:

1
2
3
4
5
6
7
8
9
10
package com.bestrookie.service;
/**
* @author bestrookie
* @date 2021/10/28 11:29 上午
*/
public interface CacheService {
String get(String key,Integer cacheType);
void set(String key,String value,Integer cacheType);
void del(String key,Integer cacheType);
}

实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.bestrookie.service;
import com.bestrookie.redis.RedisUtils;
import com.bestrookie.redis.cluster.EGM;
import com.bestrookie.redis.cluster.IIR;
/**
* @author bestrookie
* @date 2021/10/28 11:32 上午
*/
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,Integer cacheType) {
if (cacheType == 1){
return egm.gain(key);
}
if (cacheType == 2){
return iir.getData(key);
}
return redisUtils.get(key);
}

public void set(String key, String value,Integer cacheType) {
if (cacheType == 1){
egm.set(key, value);
return;
}
if (cacheType == 2){
iir.setData(key, value);
return;
}
redisUtils.set(key, value);
}

public void del(String key,Integer cacheType) {
if (cacheType == 1){
egm.delete(key);
return;
}
if (cacheType == 2){
iir.delData(key);
return;
}
redisUtils.del(key);
}
}

抽象工厂模式

image-20211029085645704

简要介绍这部分代码包括的核心内容。整个工程包结构分为三块:工厂包(factory)、工具包(util)、适配器包(adapter)

  • 工厂包:JDKPoxyFactory、JDKInvocationHandler两个类是代理类的定义和实现,这部分代码主要通过代理类和反射调用的方式获取工厂及方法调用
  • 工具包:ClassLoaderUtils类主要用于支撑反射方法调用中参数的处理。
  • 适配器包:EGMCacheAdapter、IIRCacheAdapter两个类主要是通过适配器的方式使用两个集群服务。把两个集群服务作为不同的车间,在通过抽象的代理工厂服务把每个车间转换为对应的工厂。这里强调一点,抽象工厂不一定必须使用目前的方式实现。

定义集群适配器

1
2
3
4
5
6
7
8
9
10
package com.bestrookie.adapter;
/**
* @author bestrookie
* @date 2021/10/28 2:35 下午
*/
public interface ICacheAdapter {
void set(String key ,String value);
String get(String key);
void del(String key);
}

适配器接口的作用是包装两个集群服务,在前面已经提到这两个集群服务在一些接口名称和入参方面各不相同,所以需要进行适配。同时在引入适配器后,也可以非常方便地扩展。

实现集群适配器接口

1、EGM集群

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.bestrookie.adapter;
import com.bestrookie.redis.cluster.EGM;
/**
* @author bestrookie
* @date 2021/10/28 2:36 下午
*/
public class EGMCacheAdapter implements ICacheAdapter{
private EGM egm = new EGM();
public void set(String key, String value) {
egm.set(key, value);
}

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

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

2、IIR集群

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.bestrookie.adapter;
import com.bestrookie.redis.cluster.IIR;

/**
* @author bestrookie
* @date 2021/10/28 2:38 下午
*/
public class IIRCacheAdapter implements ICacheAdapter{
private IIR iir = new IIR();

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

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

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

代理抽象工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.bestrookie.factory;
import com.bestrookie.adapter.ICacheAdapter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

/**
* @author bestrookie
* @date 2021/10/28 3:04 下午
*/
public class JDKProxyFactory {
public static <T> T getProxy(Class cacheClazz, Class<? extends ICacheAdapter> cacheAdapter) throws InstantiationException, IllegalAccessException {
InvocationHandler handler = new JDKInvocationHandler(cacheAdapter.newInstance());
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
return (T)Proxy.newProxyInstance(classLoader,new Class[]{cacheClazz},handler);
}
}
  • Class cacheClazz:在模拟的场景中,不同的系统使用的Redis服务类名可能有所不同,通过这样的方式便于实例化后的注入操作
  • Class<? extends ICacheAdapter> cacheAdapter:这个参数用于决定实例化哪套集群服务使用Redis功能

反射调用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.bestrookie.factory;
import com.bestrookie.adapter.ICacheAdapter;
import com.bestrookie.util.ClassLoaderUtils;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author bestrookie
* @date 2021/10/28 2:40 下午
*/
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);
}
}

这部分是工厂被代理实现后的核心处理类,主要包括如下功能

  • 相同适配器接口ICacheAdapter的不同Redis集群服务实现,其具体调用会在这里体现
  • 在反射调用过程中,通过入参获取需要调用的方法名称和参数,可以调用对应Redis集群中的方法

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.bestrookie.controller;
import com.bestrookie.adapter.EGMCacheAdapter;
import com.bestrookie.factory.JDKProxyFactory;
import com.bestrookie.service.CacheService;
/**
* @author bestrookie
* @date 2021/10/28 2:16 下午
*/
public class CacheController {
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
CacheService proxy = JDKProxyFactory.getProxy(CacheService.class, EGMCacheAdapter.class);
proxy.set("1","000000");
}
}

总结

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

评论