Java Design Patterns 之 抽象工厂模式 02

86 阅读3分钟

“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第3篇文章,点击查看活动详情

抽象工厂

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

案例

在公司中我们的Redis可能会经历以下的变更:

image.png

最初业务规模可能很小,单机Redis完全足以支持我们的业务,这个单机的Redis目前所有的系统都在用,随着业务规模的扩大,单机Redis不足以支撑我们的业务规模了,必须进行升级,单机 - > 集群 ,而集群环境所提供的API又与单机的服务不同,那么所有的系统都要随之调整

初级代码实现

/**
 * Description:缓存服务实现,通过if-else判断走哪个RedisServer
 *
 * @author li.hongjian
 * @email lhj502819@163.com
 * @since 2022/9/4 22:11
 */
public class CacheServiceImpl implements ICacheService {

    /**
     * 单机
     */
    private StandAloneRedis standAloneRedis = new StandAloneRedis();

    /**
     * 集群 EGM
     */
    private EGM egm = new EGM();

    /**
     * 集群IIR
     */
    private IIR iir = new IIR();


    @Override
    public String get(String key,int redisServerType) {
        if(redisServerType  == RedisServerTypeEnum.STAND_ALONE.getCode()){
            return standAloneRedis.get(key);
        }else if(redisServerType == RedisServerTypeEnum.EGM.getCode()){
            return egm.gain(key);
        }else {
            return iir.gain(key);
        }
    }

    @Override
    public void set(String key, String value, int redisServerType) {
        if(redisServerType  == RedisServerTypeEnum.STAND_ALONE.getCode()){
            standAloneRedis.set(key,value);
        }else if(redisServerType == RedisServerTypeEnum.EGM.getCode()){
            egm.set(key,value);
        }else {
            iir.set(key, value);
        }
    }

    @Override
    public void set(String key, String value, long timeout, TimeUnit timeUnit, int redisServerType) {
        if(redisServerType  == RedisServerTypeEnum.STAND_ALONE.getCode()){
            standAloneRedis.set(key,value,timeout,timeUnit);
        }else if(redisServerType == RedisServerTypeEnum.EGM.getCode()){
            egm.setEx(key,value,timeout,timeUnit);
        }else {
            iir.setExpire(key,value,timeout,timeUnit);
        }
    }

    @Override
    public void del(String key, int redisServerType) {
        if(redisServerType  == RedisServerTypeEnum.STAND_ALONE.getCode()){
            standAloneRedis.del(key);
        }else if(redisServerType == RedisServerTypeEnum.EGM.getCode()){
            egm.delete(key);
        }else {
            iir.del(key);
        }
    }

}

image.png

缺点

每次新增类型都要在ICacheService中新增适配逻辑,不满足开闭原则。

设计模式实现

设计思想

通过适配器对不同的Redis集群API进行统一。

实现

//定义适配接口,用于包装两个集群中差异化的接口名,让所有的集群提供方能够在统一的方法名称下进行操作,同时方便扩展
public interface ICacheAdapter extends ICacheService {

}
// 对 EGM Redis集群API进行适配
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);
    }
}
//对 IIR Redis集群API进行适配
public class IIRCacheAdapter implements ICacheAdapter {

    private IIR iir = new IIR();

    @Override
    public String get(String key) {
        return iir.gain(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);
    }
}
public class JdkInvocationHandler implements InvocationHandler {

    private ICacheAdapter cacheAdapter;

    /**
     * @param cacheAdapter 具体要执行的RedisServer适配器
     */
    public JdkInvocationHandler(ICacheAdapter cacheAdapter) {
        this.cacheAdapter = cacheAdapter;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //注意 :在执行方法时,执行的是传入的具体的 RedisServer  的方法
        return ICacheAdapter.class.getMethod(method.getName(), ClassLoaderUtils.getClazzByArgs(args)).invoke(cacheAdapter, args);
    }

}
//通过代理的方式,每次调用时传入具体要的使用的RedisServer API
public class JdkProxy {

    /**
     * 实现代理类,对于使用哪个集群由外部通过入参传入
     *
     * @param interfaceClass 缓存服务,提供API
     * @param cacheAdapter 具体执行的adapter,如EGMCacheAdapter、IIRCacheAdapter
     */
    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);
    }

}

调用

public class App {

    @Test
    public void test_cacheService(){
        try {
            //通过传入不同的集群类型,就可以调用不同的集群下的API
            //如果后续有扩展的需求,也可以按照这样的类型方式进行补充,同时对于改造上来说并没有改动原来的方法,降低了修改成本
            ICacheService proxyEGM = JdkProxy.getProxy(CacheServiceImpl.class, new EGMCacheAdapter());
            proxyEGM.set("name" , "壹玖");
            String nameEGM = proxyEGM.get("name");
            log.info("获取到name:{} \n" ,nameEGM);

            ICacheService proxyIIR = JdkProxy.getProxy(CacheServiceImpl.class, new IIRCacheAdapter());
            proxyIIR.set("name" , "壹玖");
            String nameIIR = proxyIIR.get("name");
            log.info("获取到name:{} \n" ,nameIIR);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}

image.png

缺点

和 工厂方法一样,随着业务的不断扩展,可能会造成类实现上的复杂度。但也可以说算不上缺点,因为可以随着其他设计方式的引入和代理类以及自动生成加载的方式降低此缺点。

借鉴

  • 《重学Java设计模式》-小傅哥