抽象工厂
为创建一组相关或者是相互依赖的对象提供一个接口,而不需要指定他们的具体实现类。
一个对象族(或是一组没有任何关系的对象)都有相同的约束,则可以使用抽象工厂模式。例如一个文本编辑器和一个图片处理器,都是软件实体,但是Linix下的文本编辑器和WINDOWS下的文本编辑器虽然功能和界面都相同,但是代码实现是不同的,图片处理器也是类似情况,也就是具有了共同的约束条件:操作系统类型,于是我们可以使用抽象工厂模式,产生不同操作系统下的编辑器和图片处理器。
栗子:
以车厂生产汽车零部件为例,A、B两家车厂分别生产不同的轮胎、发动机、制动系统。虽然生产的零件不同,型号不同。但是根本上都有共同的约束,就是轮胎、发动机、制动系统。
轮胎相关类:
|
|
|
|
发动机相关类:
|
|
|
|
制动系统相关类:
|
|
|
|
抽象车厂类:
A车厂:
B车厂:
客户类:
结果:
可以看出上面模拟了两个车厂,如果有了C厂、D厂,各自厂家生产的零部件型号种类又不相同,那么我们创建的类文件就会翻倍。这也是抽象工厂模式的一个弊端,所以实际开发中要权衡使用。
抽象工厂模式是工厂方法模式的升级版本。对比如下:
工厂方法模式 | 抽象工厂模式 |
---|---|
只有一个抽象产品类 | 有多个抽象产品类 |
具体工厂类只能创建一个具体产品类的实例 | 具体工厂类能创建多个具体产品类的实例 |
源码中的实现
在源码中, 比较典型的抽象工厂模式的例子是Java.sql包中的Connection类,在刚学习Java时我们都会学习使用JDBC链接数据库,代码大致是这样的:
上面我们是以MYSQL驱动为例,设置JDBC驱动以后,使用DriverManager.getConnection来获取具体的链接实现,然后通过这个Connection来创建一个Statement来提交SQL语句,Connection还可以创建clob, blob, sqlxml等对象,即Connection就是抽象工厂,而具体的工厂实现则在不同的数据库驱动包种。
首先我们看DriverManager中的getConnection方法 :
我们看到getConnection(String, String, String)函数调用了getConnection(Stringurl, java.util.Propertiesinfo,Class<?>caller)函数,在该函数中遍历以注册到DriverManager中的驱动,即registeredDrivers, 获取相应的驱动之后,链接到数据库,最后将该链接返回, 这样就获取到了具体的Connection, 代码为 :
那么MYSQL JDBC驱动是什么时候注册到DriverManager的呢 ?
我们看到在使用DriverManager之前,调用了以下这句代码 :
这句代码的作用就是通过反射来创建com.mysql.jdbc.Driver对象, 我们看看mysql jdbc驱动中该类的实现.
可以看到,上文中有一个静态语句块, 该语句块会在虚拟机第一次加载该类时首先执行, 该语句块的作用就是将Driver类的对象注册到DriverManager中,驱动的具体实现类为 NonRegisteringDriver。获取数据库驱动对象以后,我们需要调用驱动对象的connect(String, Properties)函数才能获取到Connection对象,我们看看 NonRegisteringDriver的connect(String, Properties)。
通过分析代码,返回的是com.mysql.jdbc.Connection类的对象, 即 return new Connection(host(props), port(props), props, database(props), url)这句, 该Connection实现了java.sql.Connection声明的接口,这就是mysql数据库连接的具体实现。
现在我们来理一下思路, java.sql包中的Statement, Clob, Blob, SQLXML都是扮演了抽象产品类族中的一员, 而java.sql.Connection则代表了抽象工厂类,里面有创建各个产品类的函数,具体的产品实现类、具体Connection工厂都封装在各个数据库驱动包中,通过Connection我们就可以创建Statement, Clob等同一类族中的对象。抽象与实现想分离,工厂可以创建一组相关的对象,客户代码使用较为简单。
优点 :
- 抽象工厂模式隔离了具体类的生产,使得客户并不需要知道什么被创建;
- 容易改变产品的系列;
- 增加新的具体工厂和产品族很方便,无须修改已有系统,符合“开闭原则”。
- 将一个系列的产品族统一到一起创建,客户代码易于使用。
缺点 :
抽象工厂模式的最大缺点就是产品族扩展非常困难,为什么这么说呢?我们以通用代码为例,如果要增加一个产品 C, 也就是说产品家族由原来的 2 个增加到 3 个,看看我们的程序有多大改动吧!抽象类 AbstractCreator 要增加一个方法 createProductC(),然后两个实现类都要修改,想想看,这严重违反了开闭原则,而且我们一直说明抽象类和接口是一个契约。