1. 前言
很多时候初期业务的蛮荒发展,也会牵动着研发对系统的建设。
预估QPS较低、系统压力较小、并发访问不大、近一年没有大动作等等,在考虑时间投入成本的前提前,并不会投入特别多的人力去构建非常完善的系统。就像对缓存的使用,往往可能只要是一种缓存即可满足现状。
但随着业务超过预期的快速发展,系统的负载能力也要随着跟上。原有的缓存已经满足不了系统需求。这时候就需要更换为更为健壮的缓存服务,比如本地缓存,redis缓存共同使用,多种选择缓存,虽然需要修改但是不能影响目前系统的运行,还要平滑过渡过去。
随着这次的升级,可以预见的问题会有:
- 很多服务用到了缓存需要一起升级。
- 需要兼容redis缓存,本地缓存,便于后续的灾备。
- 两套缓存提供的接口和方法各有差异,需要做适配。
- 不能影响到目前正常运行的系统。
2. 现状
- 已有接口CacheProviderService
public interface CacheProviderService {
/**
* 查询缓存
*
* @param key 缓存键 不可为空
* @return T
* @author zhouxinlei
* @date 2020-01-27 20:19:27
*/
<T> T get(String key);
/**
* 查询缓存
*
* @param key 缓存键 不可为空
* @param function 如没有缓存,调用该callable函数返回对象 可为空
* @return T
*/
<T> T get(String key, Function<String, T> function);
/**
* 查询缓存
*
* @param key 缓存键 不可为空
* @param function 如没有缓存,调用该callable函数返回对象 可为空
* @param funcParam function函数的调用参数
* @return T
*/
<T, M> T get(String key, Function<M, T> function, M funcParam);
/**
* 查询缓存
*
* @param key 缓存键 不可为空
* @param function 如没有缓存,调用该callable函数返回对象 可为空
* @param expireTime 过期时间(单位:毫秒) 可为空
* @return T
**/
<T> T get(String key, Function<String, T> function, Long expireTime);
/**
* 查询缓存
*
* @param key 缓存键 不可为空
* @param function 如没有缓存,调用该callable函数返回对象 可为空
* @param funcParam function函数的调用参数
* @param expireTime 过期时间(单位:毫秒) 可为空
* @return T
**/
<T, M> T get(String key, Function<M, T> function, M funcParam, Long expireTime);
/**
* 设置缓存键值
*
* @param key 缓存键 不可为空
* @param obj 缓存值 不可为空
* @return void
**/
<T> void set(String key, T obj);
/**
* 设置缓存键值
*
* @param key 缓存键 不可为空
* @param obj 缓存值 不可为空
* @param expireTime 过期时间(单位:毫秒) 可为空
* @return void
**/
<T> void set(String key, T obj, Long expireTime);
/**
* 自增长
*
* @param key key值
* @param delta 自增间距
* @return Long
*/
Long increment(String key, long delta);
/**
* 自减
*
* @param key key值
* @param delta 自减间距
* @return Long
* @author zhouxinlei
* @date 2019-10-11 16:23:58
*/
Long decrement(String key, long delta);
/**
* 移除缓存
*
* @param key 缓存键 不可为空
* @return void
**/
void remove(String key);
/**
* 是否存在缓存
*
* @param key 缓存键 不可为空
* @return boolean
**/
boolean contains(String key);
}
- 缓存接口实现类CacheProviderImpl
@Component
@Slf4j
public class CacheProviderImpl implements CacheProviderService {
private final RedisTemplate<String, Object> redisTemplate;
private ValueOperations<String, Object> valueOperations;
private final static long CACHE_MINUTE = 60;
public CacheProviderImpl(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
@PostConstruct
public void initRedisOperation() {
valueOperations = redisTemplate.opsForValue();
}
@Override
public <T> T get(String key) {
return get(key, null, null, CACHE_MINUTE);
}
@Override
public <T> T get(String key, Function<String, T> function) {
return get(key, function, key, CACHE_MINUTE);
}
@Override
public <T, M> T get(String key, Function<M, T> function, M funcParam) {
return get(key, function, funcParam, CACHE_MINUTE);
}
@Override
public <T> T get(String key, Function<String, T> function, Long expireTime) {
return get(key, function, key, expireTime);
}
@Override
public <T, M> T get(String key, Function<M, T> function, M funcParm, Long expireTime) {
T obj = null;
if (StringUtils.isEmpty(key)) {
return null;
}
expireTime = getExpireTime(expireTime);
try {
obj = (T) valueOperations.get(key);
if (function != null && obj == null) {
obj = function.apply(funcParm);
if (obj != null) {
//设置缓存信息
set(key, obj, expireTime);
}
}
} catch (Exception e) {
log.error(e.getMessage());
}
return obj;
}
@Override
public <T> void set(String key, T obj) {
set(key, obj, CACHE_MINUTE);
}
@Override
public <T> void set(String key, T obj, Long expireTime) {
if (StringUtils.isEmpty(key)) {
return;
}
if (obj == null) {
return;
}
valueOperations.set(key, obj,expireTime,TimeUnit.SECONDS);
}
@Override
public Long increment(String key, long delta) {
return valueOperations.increment(key, delta);
}
@Override
public Long decrement(String key, long delta) {
return valueOperations.decrement(key, delta);
}
@Override
public void remove(String key) {
if (StringUtils.isEmpty(key)) {
return;
}
redisTemplate.delete(key);
}
@Override
public boolean contains(String key) {
boolean exists = false;
if (StringUtils.isEmpty(key)) {
return false;
}
Object obj = get(key);
if (obj != null) {
exists = true;
}
return exists;
}
/**
* 获取过期时间 单位:毫秒
*
* @param expireTime 传人的过期时间 单位毫秒 如小于1分钟,默认为10分钟
**/
private Long getExpireTime(Long expireTime) {
Long result = expireTime;
if (expireTime == null || expireTime < CACHE_MINUTE) {
result = CACHE_MINUTE;
}
return result;
}
}
3. 实现
思路,我这边想到两种实现思路,一种通过spring注解Qualifier实现,另一种通过抽象工厂模式+适配器模式+JDK动态代理模式来实现
- 思路流程图
3.1 Qualifier方式
这边新加GuavaCache本地缓存,实现CacheProviderService
接口,这样CacheProviderService有两个实现类,通过
@Qualifier("redisCacheProviderImpl")
private CacheProviderService cacheProviderService;
注入使用,指定bean选择合适的实现业务缓存
3.2 抽象工厂模式+适配器模式+JDK动态代理模式实现
3.2.1 简介实现
涉及的部分核心功能代码
- CacheProviderService即是上面的接口定义
- ICacheAdapter,定义了适配接口,分别包装两个缓存中差异化的接口名
GuavaCacheAdapter、RedisCacheAdapter - JdkCacheProxy、JdkInvocationHandler,是代理类的定义和实现,这部分也就是抽象工厂的另外一种实现方式。通过这样的方式可以很好的把原有操作Redis的方法进行代理操作,通过控制不同的入参对象,控制缓存的使用。
3.2.2 代码实现
- ICacheAdapter
import java.util.function.Function;
/**
* description:
*
* @author: zhouxinlei
* @date: 2020-07-08 10:01:32
*/
public interface ICacheAdapter {
/**
* 查询缓存
*
* @param key 缓存键 不可为空
* @return T
* @author zhouxinlei
* @date 2020-01-27 20:19:27
*/
<T> T get(String key);
/**
* 查询缓存
*
* @param key 缓存键 不可为空
* @param function 如没有缓存,调用该callable函数返回对象 可为空
* @return T
*/
<T> T get(String key, Function<String, T> function);
/**
* 查询缓存
*
* @param key 缓存键 不可为空
* @param function 如没有缓存,调用该callable函数返回对象 可为空
* @param funcParam function函数的调用参数
* @return T
*/
<T, M> T get(String key, Function<M, T> function, M funcParam);
/**
* 查询缓存
*
* @param key 缓存键 不可为空
* @param function 如没有缓存,调用该callable函数返回对象 可为空
* @param expireTime 过期时间(单位:毫秒) 可为空
* @return T
**/
<T> T get(String key, Function<String, T> function, Long expireTime);
/**
* 查询缓存
*
* @param key 缓存键 不可为空
* @param function 如没有缓存,调用该callable函数返回对象 可为空
* @param funcParam function函数的调用参数
* @param expireTime 过期时间(单位:毫秒) 可为空
* @return T
**/
<T, M> T get(String key, Function<M, T> function, M funcParam, Long expireTime);
/**
* 设置缓存键值
*
* @param key 缓存键 不可为空
* @param obj 缓存值 不可为空
* @return void
**/
<T> void set(String key, T obj);
/**
* 设置缓存键值
*
* @param key 缓存键 不可为空
* @param obj 缓存值 不可为空
* @param expireTime 过期时间(单位:毫秒) 可为空
* @return void
**/
<T> void set(String key, T obj, Long expireTime);
/**
* 自增长
*
* @param key key值
* @param delta 自增间距
* @return Long
*/
Long increment(String key, long delta);
/**
* 自减
*
* @param key key值
* @param delta 自减间距
* @return Long
* @author zhouxinlei
* @date 2019-10-11 16:23:58
*/
Long decrement(String key, long delta);
/**
* 移除缓存
*
* @param key 缓存键 不可为空
* @return void
**/
void remove(String key);
/**
* 是否存在缓存
*
* @param key 缓存键 不可为空
* @return boolean
**/
boolean contains(String key);
}
- GuavaCacheAdapter
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Maps;
import com.sparksys.commons.core.cache.ICacheAdapter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.LongAdder;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* description:Guava Cache
*
* @author zhouxinlei
* @date 2020/6/17 0017
*/
@Slf4j
@Component
public class GuavaCacheAdapter implements ICacheAdapter {
private static final Map<String, Cache<String, Object>> CACHE_CONCURRENT_MAP = Maps.newConcurrentMap();
private static final long CACHE_MAXIMUM_SIZE = 100;
private static final long CACHE_MINUTE = 10 * 60;
private static final Lock LOCK = new ReentrantLock();
static {
Cache<String, Object> cacheContainer = CacheBuilder.newBuilder()
.maximumSize(CACHE_MAXIMUM_SIZE)
//最后一次写入后的一段时间移出
.expireAfterWrite(CACHE_MINUTE, TimeUnit.SECONDS)
//.expireAfterAccess(CACHE_MINUTE, TimeUnit.MILLISECONDS) //最后一次访问后的一段时间移出
.recordStats()//开启统计功能
.build();
CACHE_CONCURRENT_MAP.put(String.valueOf(CACHE_MINUTE), cacheContainer);
}
/**
* 查询缓存
*
* @param key 缓存键 不可为空
**/
@Override
public <T> T get(String key) {
return get(key, null, null, CACHE_MINUTE);
}
/**
* 查询缓存
*
* @param key 缓存键 不可为空
* @param function 如没有缓存,调用该callable函数返回对象 可为空
**/
@Override
public <T> T get(String key, Function<String, T> function) {
return get(key, function, key, CACHE_MINUTE);
}
/**
* 查询缓存
*
* @param key 缓存键 不可为空
* @param function 如没有缓存,调用该callable函数返回对象 可为空
* @param funcParam function函数的调用参数
**/
@Override
public <T, M> T get(String key, Function<M, T> function, M funcParam) {
return get(key, function, funcParam, CACHE_MINUTE);
}
/**
* 查询缓存
*
* @param key 缓存键 不可为空
* @param function 如没有缓存,调用该callable函数返回对象 可为空
* @param expireTime 过期时间(单位:毫秒) 可为空
**/
@Override
public <T> T get(String key, Function<String, T> function, Long expireTime) {
return get(key, function, key, expireTime);
}
/**
* 查询缓存
*
* @param key 缓存键 不可为空
* @param function 如没有缓存,调用该callable函数返回对象 可为空
* @param funcParam function函数的调用参数
* @param expireTime 过期时间(单位:毫秒) 可为空
**/
@Override
public <T, M> T get(String key, Function<M, T> function, M funcParam, Long expireTime) {
T obj = null;
if (StringUtils.isEmpty(key)) {
return null;
}
expireTime = getExpireTime(expireTime);
Cache<String, Object> cacheContainer = getCacheContainer(expireTime);
try {
if (function == null) {
obj = (T) cacheContainer.getIfPresent(key);
} else {
obj = (T) cacheContainer.get(key, () -> function.apply(funcParam));
}
} catch (Exception e) {
log.error(e.getMessage());
}
return obj;
}
/**
* 设置缓存键值 直接向缓存中插入值,这会直接覆盖掉给定键之前映射的值
*
* @param key 缓存键 不可为空
* @param obj 缓存值 不可为空
**/
@Override
public <T> void set(String key, T obj) {
set(key, obj, CACHE_MINUTE);
}
/**
* 设置缓存键值 直接向缓存中插入值,这会直接覆盖掉给定键之前映射的值
*
* @param key 缓存键 不可为空
* @param obj 缓存值 不可为空
* @param expireTime 过期时间(单位:毫秒) 可为空
**/
@Override
public <T> void set(String key, T obj, Long expireTime) {
if (StringUtils.isEmpty(key)) {
return;
}
if (obj == null) {
return;
}
expireTime = getExpireTime(expireTime);
Cache<String, Object> cacheContainer = getCacheContainer(expireTime);
cacheContainer.put(key, obj);
}
@Override
public Long increment(String key, long delta) {
Long expireTime = getExpireTime(CACHE_MINUTE);
Cache<String, Object> cacheContainer = getCacheContainer(expireTime);
Supplier<LongAdder> function = LongAdder::new;
LongAdder longAdder;
try {
longAdder = (LongAdder) cacheContainer.get(key, function::get);
longAdder.add(delta);
cacheContainer.put(key, longAdder);
return longAdder.longValue();
} catch (ExecutionException e) {
e.printStackTrace();
}
return 0L;
}
@Override
public Long decrement(String key, long delta) {
Long expireTime = getExpireTime(CACHE_MINUTE);
Cache<String, Object> cacheContainer = getCacheContainer(expireTime);
Supplier<LongAdder> function = LongAdder::new;
LongAdder longAdder;
try {
longAdder = (LongAdder) cacheContainer.get(key, function::get);
longAdder.add(-delta);
cacheContainer.put(key, longAdder);
return longAdder.longValue();
} catch (ExecutionException e) {
e.printStackTrace();
}
return 0L;
}
/**
* 移除缓存
*
* @param key 缓存键 不可为空
**/
@Override
public void remove(String key) {
if (StringUtils.isEmpty(key)) {
return;
}
long expireTime = getExpireTime(CACHE_MINUTE);
Cache<String, Object> cacheContainer = getCacheContainer(expireTime);
cacheContainer.invalidate(key);
}
/**
* 是否存在缓存
*
* @param key 缓存键 不可为空
**/
@Override
public boolean contains(String key) {
boolean exists = false;
if (StringUtils.isEmpty(key)) {
return false;
}
Object obj = get(key);
if (obj != null) {
exists = true;
}
return exists;
}
private Cache<String, Object> getCacheContainer(Long expireTime) {
Cache<String, Object> cacheContainer;
if (expireTime == null) {
return null;
}
String mapKey = String.valueOf(expireTime);
if (CACHE_CONCURRENT_MAP.containsKey(mapKey)) {
cacheContainer = CACHE_CONCURRENT_MAP.get(mapKey);
return cacheContainer;
}
LOCK.lock();
try {
cacheContainer = CacheBuilder.newBuilder()
.maximumSize(CACHE_MAXIMUM_SIZE)
//最后一次写入后的一段时间移出
.expireAfterWrite(expireTime, TimeUnit.SECONDS)
//.expireAfterAccess(AppConst.CACHE_MINUTE, TimeUnit.MILLISECONDS) //最后一次访问后的一段时间移出
.recordStats()//开启统计功能
.build();
CACHE_CONCURRENT_MAP.put(mapKey, cacheContainer);
} finally {
LOCK.unlock();
}
return cacheContainer;
}
/**
* 获取过期时间 单位:秒
*
* @param expireTime 传人的过期时间 单位秒 如小于1分钟,默认为10分钟
**/
private Long getExpireTime(Long expireTime) {
Long result = expireTime;
if (expireTime == null || expireTime < CACHE_MINUTE / 10) {
result = CACHE_MINUTE;
}
return result;
}
}
- RedisCacheAdapter
import com.sparksys.commons.core.cache.ICacheAdapter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.annotation.PostConstruct;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
/**
* description: redis缓存实现
*
* @author zhouxinlei
* @date 2020-05-24 13:28:55
*/
@Component
@Slf4j
public class RedisCacheAdapter implements ICacheAdapter {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private ValueOperations<String, Object> valueOperations;
private final static long CACHE_MINUTE = 60;
@PostConstruct
public void initRedisOperation() {
valueOperations = redisTemplate.opsForValue();
}
@Override
public <T> T get(String key) {
return get(key, null, null, CACHE_MINUTE);
}
@Override
public <T> T get(String key, Function<String, T> function) {
return get(key, function, key, CACHE_MINUTE);
}
@Override
public <T, M> T get(String key, Function<M, T> function, M funcParam) {
return get(key, function, funcParam, CACHE_MINUTE);
}
@Override
public <T> T get(String key, Function<String, T> function, Long expireTime) {
return get(key, function, key, expireTime);
}
@Override
public <T, M> T get(String key, Function<M, T> function, M funcParm, Long expireTime) {
T obj = null;
if (StringUtils.isEmpty(key)) {
return null;
}
expireTime = getExpireTime(expireTime);
try {
obj = (T) valueOperations.get(key);
if (function != null && obj == null) {
obj = function.apply(funcParm);
if (obj != null) {
//设置缓存信息
set(key, obj, expireTime);
}
}
} catch (Exception e) {
log.error(e.getMessage());
}
return obj;
}
@Override
public <T> void set(String key, T obj) {
set(key, obj, CACHE_MINUTE);
}
@Override
public <T> void set(String key, T obj, Long expireTime) {
if (StringUtils.isEmpty(key)) {
return;
}
if (obj == null) {
return;
}
expireTime = getExpireTime(expireTime);
valueOperations.set(key, obj, expireTime, TimeUnit.SECONDS);
}
@Override
public Long increment(String key, long delta) {
return valueOperations.increment(key, delta);
}
@Override
public Long decrement(String key, long delta) {
return valueOperations.decrement(key, delta);
}
@Override
public void remove(String key) {
if (StringUtils.isEmpty(key)) {
return;
}
redisTemplate.delete(key);
}
@Override
public boolean contains(String key) {
boolean exists = false;
if (StringUtils.isEmpty(key)) {
return false;
}
Object obj = get(key);
if (obj != null) {
exists = true;
}
return exists;
}
/**
* 获取过期时间 单位:毫秒
*
* @param expireTime 传人的过期时间 单位毫秒 如小于1分钟,默认为10分钟
**/
private Long getExpireTime(Long expireTime) {
Long result = expireTime;
if (expireTime == null || expireTime < CACHE_MINUTE) {
result = CACHE_MINUTE;
}
return result;
}
}
- ClassLoaderUtils工具类
import cn.hutool.core.util.ClassLoaderUtil;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* description: 类加载工具类
*
* @author: zhouxinlei
* @date: 2020-07-08 11:22:32
*/
public class ClassLoaderUtils extends ClassLoaderUtil {
private static final Set<Class> PRIMITIVE_SET = new HashSet<>();
static {
PRIMITIVE_SET.add(Integer.class);
PRIMITIVE_SET.add(Long.class);
PRIMITIVE_SET.add(Float.class);
PRIMITIVE_SET.add(Byte.class);
PRIMITIVE_SET.add(Short.class);
PRIMITIVE_SET.add(Double.class);
PRIMITIVE_SET.add(Character.class);
PRIMITIVE_SET.add(Boolean.class);
}
/**
* 实例化一个对象(只检测默认构造函数,其它不管)
*
* @param clazz 对象类
* @param <T> 对象具体类
* @return 对象实例
* @throws Exception 没有找到方法,或者无法处理,或者初始化方法异常等
*/
public static <T> T newInstance(Class<T> clazz) throws Exception {
if (PRIMITIVE_SET.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);
}
}
- JdkInvocationHandler
import com.sparksys.commons.core.utils.ClassLoaderUtils;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* description: JDK动态代理实现类
*
* @author: zhouxinlei
* @date: 2020-07-08 10:09:45
*/
public class JdkInvocationHandler implements InvocationHandler {
private final 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);
}
}
- JdkCacheProxy
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
/**
* description:
*
* @author: zhouxinlei
* @date: 2020-07-08 10:12:12
*/
public class JdkCacheProxy {
public static <T> T getProxy(Class<T> interfaceClass, ICacheAdapter cacheAdapter) {
InvocationHandler invocationHandler = new JdkInvocationHandler(cacheAdapter);
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Class<?>[] classes = interfaceClass.getInterfaces();
//noinspection unchecked
return (T) Proxy.newProxyInstance(classLoader, new Class[]{classes[0]}, invocationHandler);
}
}
4 总结
- 抽象工厂模式:所要解决的问题就是在一个产品族,存在多个不同类型的产品(Redis集群、操作系统)情况下,接口选择的问题。而这种场景在业务开发中也是非常多见的,只不过可能有时候没有将它们抽象化出来。
- 你的代码只是被if else埋上了!当你知道什么场景下何时可以被抽象工程优化代码,那么你的代码层级结构以及满足业务需求上,都可以得到很好的完成功能实现并提升扩展性和优雅度。
- 那么这个设计模式满足了;单一职责、开闭原则、解耦等优点,但如果说随着业务的不断拓展,可能会造成类实现上的复杂度。但也可以说算不上缺点,因为可以随着其他设计方式的引入和代理类以及自动生成加载的方式降低此项缺点。
评论区