抽象工厂(Abstract Factory)

Intent

提供一个接口,用于创建 相关的对象家族

Class Diagram

抽象工厂模式创建的是对象家族,也就是很多对象而不是一个对象,并且这些对象是相关的,也就是说必须一起创建出来。而工厂方法模式只是用于创建一个对象,这和抽象工厂模式有很大不同。

抽象工厂模式用到了工厂方法模式来创建单一对象,AbstractFactory 中的 createProductA() 和 createProductB() 方法都是让子类来实现,这两个方法单独来看就是在创建一个对象,这符合工厂方法模式的定义。

至于创建对象的家族这一概念是在 Client 体现,Client 要通过 AbstractFactory 同时调用两个方法来创建出两个对象,在这里这两个对象就有很大的相关性,Client 需要同时创建出这两个对象。

从高层次来看,抽象工厂使用了组合,即 Cilent 组合了 AbstractFactory,而工厂方法模式使用了继承。

简单场景例如:不同系统內的回车换行

  1. Uniⅸ系统里,每行结尾只有<换行>,即\n

  2. Windows系统里面,每行结尾是<換行><回车>,即\n\r

  3. Mac系统里,每行结尾是<回车>

Implementation

public class AbstractProductA {
}
public class AbstractProductB {
}
public class ProductA1 extends AbstractProductA {
}
public class ProductA2 extends AbstractProductA {
}
public class ProductB1 extends AbstractProductB {
}
public class ProductB2 extends AbstractProductB {
}
public abstract class AbstractFactory {
    abstract AbstractProductA createProductA();
    abstract AbstractProductB createProductB();
}
public class ConcreteFactory1 extends AbstractFactory {
    AbstractProductA createProductA() {
        return new ProductA1();
    }
​
    AbstractProductB createProductB() {
        return new ProductB1();
    }
}
public class ConcreteFactory2 extends AbstractFactory {
    AbstractProductA createProductA() {
        return new ProductA2();
    }
​
    AbstractProductB createProductB() {
        return new ProductB2();
    }
}
public class Client {
    public static void main(String[] args) {
        AbstractFactory abstractFactory = new ConcreteFactory1();
        AbstractProductA productA = abstractFactory.createProductA();
        AbstractProductB productB = abstractFactory.createProductB();
        // do something with productA and productB
    }
}

Business Scenario

随着业务超过预期的快速发展,系统的负载能力也要随着跟上。原有的单机 Redis已经满足不了系统需求。这时候就需要更换为更为健壮的 Redis集群服务,虽然需要修改但是不能影响目前系统的运行,还要平滑过渡过去。

随着这次的升级,可以预见的问题会有很多:

  1. 服务用到了 Redis需要一起升级到集群。

  2. 需要兼容集群A和集群B,便于后续的灾备。

  3. 两套集群提供的接口和方法各有差异,需要做适配。

  4. 不能影响到目前正常运行的系统。

使用抽象工厂实现:

两个不同redis集群的实现

package org.itstack.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;
​
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);
    }
}
package org.itstack.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;
​
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);
    }
​
}

两个集群我们将他们作为产品,我们将产品的方法抽象出来,另外产品还一个作用是兼容不同的方法

package org.itstack.demo.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 org.itstack.demo.desgin.factory.impl;
​
import org.itstack.demo.desgin.factory.ICacheAdapter;
import org.itstack.demo.design.matter.EGM;
​
import java.util.concurrent.TimeUnit;
​
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);
    }
}
package org.itstack.demo.desgin.factory.impl;
​
import org.itstack.demo.desgin.factory.ICacheAdapter;
import org.itstack.demo.design.matter.IIR;
​
import java.util.concurrent.TimeUnit;
​
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);
    }
​
}

抽象出来工厂创建产品,这里相当于一个工厂生产两个产品,这里我们用jdk的动态代理的方式实现(如果不通过代理,也可以跟Implementation的实现方法一样)

package org.itstack.demo.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 org.itstack.demo.desgin.factory;
​
import org.itstack.demo.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;
    }
​
    // 代理类的方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return ICacheAdapter.class.getMethod(method.getName(), ClassLoaderUtils.getClazzByArgs(args)).invoke(cacheAdapter, args);
    }
​
}

测试

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

summary

抽象工厂模式,所要解决的问题就是在一个产品族,存在多个不同类型的产品( Redis集群、操作系统)情况下,接口选择的问题。而这种场景在业务开发中也是非常多见的,只不过可能有时候没有将它们抽象化出来。