介绍
在工厂方法模式中,我们使用一个工厂创建一个产品,也就是说一个具体的工厂对应一个具体的产品。但是有时候我们需要一个工厂能够提供多个产品对象,而不是单一的对象,这个时候我们就需要使用抽象工厂模式。
在讲解抽象工厂模式之前,我们需要厘清两个概念:
产品等级结构
- 产品的等级结构也就是产品的继承结构。例如一个为空调的抽象类,它有海尔空调、格力空调、美的空调等一系列的子类,那么这个抽象类空调和它的子类就构成了一个产品等级结构。
产品族
- 在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品。比如,海尔工厂生产的海尔空调、海尔冰箱、海尔洗衣机都属于同一产品族的产品。
产品等级结构和产品族结构示意图如下:
概念
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
抽象工厂模式结构图:
- 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等同一产品族中的对象。抽象与实现想分离,工厂可以创建一组相关的对象,客户端代码使用较为简单。