抽象工厂模式

562 阅读7分钟

介绍

在工厂方法模式中,我们使用一个工厂创建一个产品,也就是说一个具体的工厂对应一个具体的产品。但是有时候我们需要一个工厂能够提供多个产品对象,而不是单一的对象,这个时候我们就需要使用抽象工厂模式。

在讲解抽象工厂模式之前,我们需要厘清两个概念:

产品等级结构

  • 产品的等级结构也就是产品的继承结构。例如一个为空调的抽象类,它有海尔空调、格力空调、美的空调等一系列的子类,那么这个抽象类空调和它的子类就构成了一个产品等级结构。

产品族

  • 在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品。比如,海尔工厂生产的海尔空调、海尔冰箱、海尔洗衣机都属于同一产品族的产品。

产品等级结构和产品族结构示意图如下:

概念

提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

抽象工厂模式结构图:

  • AbstractFactory:抽象工厂。抽象工厂定义了一组方法用来生产不同的产品。
  • ConcreateFactory:具体工厂。具体工厂是用于生产不同的产品族。要创建一个产品,用户只需要使用其中一个工厂,完全不需要实例化任何产品对象。
  • AbstractProduct:抽象产品。这是一个产品家族,每一个具体工厂都能够生产一整组产品。
  • Product:具体产品。

模式实现

小马哥附近新开了两家工厂「小太阳工厂和小月亮工厂」,业务都是一样的,都用来生产自家的空调、冰箱和洗衣机。

  • 抽象产品(空调、冰箱、洗衣机)
public abstract class BaseAirConditioner {

    /**
     * 降低室内温度
     */
    public abstract void  cool();
}
public abstract class BaseRefrigerator {

    /**
     * 储存食物
     */
    public abstract void saveFoods();
}
public abstract class BaseWashingMachine {

    /**
     * 洗衣服
     */
    public abstract void washClothes();
}
  • 具体产品(太阳牌「空调、冰箱、洗衣机」;月亮牌「空调 、冰箱、洗衣机」)
//空调
public class SunAirConditioner extends BaseAirConditioner{

    @Override
    public void cool() {
        System.out.println("太阳牌空调开始给室内降低温度");
    }
}
public class MoonAirConditioner extends BaseAirConditioner{

    @Override
    public void cool() {
        System.out.println("月亮牌空调开始给室内降低温度");
    }
}

//冰箱
public class SunRefrigerator extends BaseRefrigerator {

    @Override
    public void saveFoods() {
        System.out.println("太阳牌冰箱来储存更多食物");
    }
}
public class MoonRefrigerator extends BaseRefrigerator {

    @Override
    public void saveFoods() {
        System.out.println("月亮牌冰箱来储存更多食物");
    }
}

//洗衣机
public class SunWashingMachine extends BaseWashingMachine {

    @Override
    public void washClothes() {
        System.out.println("太阳牌洗衣机开始洗大量的衣服");
    }
}
public class MoonWashingMachine extends BaseWashingMachine{

    @Override
    public void washClothes() {
        System.out.println("月亮牌洗衣机开始洗大量的衣服");
    }
}
  • 抽象工厂(AbstractFactory)
public interface IFactory {

    /**
     * 生产空调
     * @return 空调
     */
    BaseAirConditioner createAirConditioner();

    /**
     * 生产冰箱
     * @return 冰箱
     */
    BaseRefrigerator createRefrigerator();

    /**
     * 生产洗衣机
     * @return 洗衣机
     */
    BaseWashingMachine createWashingMachine();
}

小太阳工厂和小月亮工厂(ConcreteFactory):

/**
 * 小太阳工厂
 */
public class SunFactory implements IFactory {

    @Override
    public BaseAirConditioner createAirConditioner() {
        return new SunAirConditioner();
    }

    @Override
    public BaseRefrigerator createRefrigerator() {
        return new SunRefrigerator();
    }

    @Override
    public BaseWashingMachine createWashingMachine() {
        return new SunWashingMachine();
    }
}
/**
 * 小月亮工厂
 */
public class MoonFactory implements IFactory {

    @Override
    public BaseAirConditioner createAirConditioner() {
        return new MoonAirConditioner();
    }

    @Override
    public BaseRefrigerator createRefrigerator() {
        return new MoonRefrigerator();
    }

    @Override
    public BaseWashingMachine createWashingMachine() {
        return new MoonWashingMachine();
    }
}

测试类:

public class Test {

    public static void main(String[] args) {
        IFactory iFactory = new SunFactory();
        
        //获取空调
        BaseAirConditioner airConditioner = iFactory.createAirConditioner();
        airConditioner.cool();

        //获取冰箱
        BaseRefrigerator refrigerator = iFactory.createRefrigerator();
        refrigerator.saveFoods();

        //获取洗衣机
        BaseWashingMachine washingMachine = iFactory.createWashingMachine();
        washingMachine.washClothes();
    }
}

UML类图:

优点

  • 抽象工厂隔离了具体类的生成,客户端不需要知道什么对象被创建。所有的具体工厂都实现了抽象工厂定义的公共接口,因此只需要改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。
  • 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。

缺点:

  • 增加新的产品类比较麻烦。如果需要添加一个对象到产品族时,需要更改接口及其所有子类,这必然带来很大的麻烦。

适用场景

  • 系统中有多余一个的产品族,而每次只使用其中某一产品族。
  • 属于同一产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来。

总结

抽象工厂最大的好处是易于交换产品系列,由于具体工厂类,例如:

IComputerFactory iComputerFactory = new MoonComputerFactory();

在一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂即可使用不同的产品配置。

它让具体的创建实例过程与客户端分离,客户端通过它们的抽象接口操纵实例,产品的具体类也被具体工厂的实现分离,不会出现在客服端代码中。但是当我们增加一个产品类就需要修改抽象工厂,那么所有的具体工厂类均会被修改。

源码分析(JDK 13; mysql-connector 8.0.19)

在源码中, 比较典型的抽象工厂模式的例子是Java.sql包中的Connection类:

抽象工厂 具体工厂
Connection ConnectionImpl
抽象产品族 具体产品
Clob Clob
Bolb Blob
Statement StatementImpl
PreparedStatement PreparedStatementWrapper
CallableStatement CallableStatement
... ...

其中抽象产品「Clob、Blob、Callablement」与具体产品类重名,但是位于不同的Package。Connection这个抽象工厂里的产品族没有写全,其它的用了...替代。

在刚学习Java时我们都会学习使用JDBC链接数据库,代码大致是这样的:

try {  
    Connection con = null; // 定义一个MySql链接对象  
    Class.forName("com.mysql.jdbc.cj.Driver").newInstance(); // MySql驱动  
    con = DriverManager.getConnection(  
            "jdbc:mysql://localhost:3306/test", "root", "root"); // 链接本地MySql  
  
    Statement stmt; // 创建声明  
    stmt = con.createStatement();  
  
    // 新增一条数据  
    stmt.executeUpdate("INSERT INTO user (username, password) VALUES ('asia', '123')");  
    ResultSet res = stmt.executeQuery("select 1643601");  
    // 代码省略...  
} catch (Exception e) {  
    e.printStackTrace();  
}

来看一下通过DriverManager类的getConnection(xxx)方法获取具体工厂的源码:

//获得具体工厂
DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root")

进入到DriverManager类:

public class DriverManager {
    
    //...(省略无关代码,下同)
    public static Connection getConnection(String url,
        String user, String password) throws SQLException {
        java.util.Properties info = new java.util.Properties();

        if (user != null) {
            info.put("user", user);
        }
        if (password != null) {
            info.put("password", password);
        }

        
        return (getConnection(url, info, Reflection.getCallerClass()));
    }
    
    @CallerSensitive
    public static Connection getConnection(String url,
        String user, String password) throws SQLException {
        
        //...
        //因为代码注册了驱动,所以会进入循坏
        for (DriverInfo aDriver : registeredDrivers) {
            // If the caller does not have permission to load the driver then
            // skip it.
            if (isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    println("    trying " + aDriver.driver.getClass().getName());
                    //代码进入到这里 
                    //调用Driver类的connect(url, info),会走到其子类NonRegisteringDriver
                    Connection con = aDriver.driver.connect(url, info); 
                    if (con != null) {
                        // Success!
                        println("getConnection returning " + aDriver.driver.getClass().getName());
                        return (con);
                    }
                } catch (SQLException ex) {
                    if (reason == null) {
                        reason = ex;
                    }
                }

            } else {
                println("    skipping: " + aDriver.getClass().getName());
            }

        }
    }
}

com.mysql.cj.jdbc.NonRegisteringDriver类:

public class NonRegisteringDriver implements java.sql.Driver {

    //...
    public java.sql.Connection connect(String url, Properties info) throws SQLException {
        //...
        try {
            ConnectionUrl conStr = ConnectionUrl.getConnectionUrlInstance(url, info);
            switch (conStr.getType()) {
                case SINGLE_CONNECTION: 
                    //代码进入到这里,获取具体工厂实例
                    return com.mysql.cj.jdbc.ConnectionImpl.getInstance(conStr.getMainHost());
            //...
            default:
                return null;
        }

    } catch (UnsupportedConnectionStringException e) {
        // when Connector/J can't handle this connection string the Driver must return null
        return null;

    } catch (CJException ex) {
        throw ExceptionFactory.createException(UnableToConnectException.class,
                Messages.getString("NonRegisteringDriver.17", new Object[] { ex.toString() }),ex);
    }
}
}

com.mysql.cj.jdbc.ConnectionImpl类,最终返回ConnectionImpl类的实例对象:

 public static JdbcConnection getInstance(HostInfo hostInfo) throws SQLException {
    //创建具体工厂ConnectionImpl并返回
    return new ConnectionImpl(hostInfo);
}

来看一下Connection这个抽象工厂里声明的产品族「部分」:

Statement createStatement() throws SQLException;
PreparedStatement prepareStatement(String sql)
        throws SQLException;
CallableStatement prepareCall(String sql) throws SQLException;        
Blob createBlob() throws SQLException;
Clob createClob() throws SQLException;
SQLXML createSQLXML() throws SQLException;

UML类图:

现在我们来理一下思路, java.sql.Connection类中的Statement,PreparedStatement,CallableStatement,Clob, Blob, SQLXML都是扮演了抽象产品类族中的一员,而java.sql.Connection则代表了抽象工厂类,里面有创建各个产品类的函数,具体的产品实现类、具体Connection工厂都封装在各个数据库驱动包中,通过Connection我们就可以创建Statement, Clob等同一产品族中的对象。抽象与实现想分离,工厂可以创建一组相关的对象,客户端代码使用较为简单。