什么是抽象工厂模式?
抽象工厂模式(Abstract Factory),提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。[DP]
前两节分别学习了简单工厂模式与工厂方法模式,其中,工厂方法模式是为了解决简单工厂模式的扩展问题而出现的,但随之而来的就是其只能够“生产”同一类产品(产品族),如果要增加新的产品族,就比较麻烦。
抽象工厂模式就是为解决这个问题而出现的,来看看它的UML类图:
该模式是工厂方法模式的升级版本,主要是为了解决多产品族的问题。当需要增加新的业务时,增加新的产品类即可。
实现
为了能够更好的区分工厂方法模式和抽象工厂模式,我们用两种方式来实现数据库访问。要求如下:“由于不同的客户要求,网站使用的数据库是不一样的,如何设计系统使得可以维护使用Access和使用SQL Server的数据库。”
工厂方法模式
假设现在系统只需要操作User表,两种数据库:Sql Server与Access。其UML类图如下:
User类
User类只有id和name
public class User {private int _id;private String name;public int getId() {return _id;}public void setId(int id) {this._id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}}
IUser接口
因为不同数据库的SQL语句是不一样的,因此该接口是为了解耦客户端与底层数据库。为简易代码,以插入/获取User为例:
public interface IUser {public void insert(User user);public User getUser(int id);}
SqlserverUser类
使用Sqlserver数据库时操作User:
public class SqlserverUser implements IUser {@Overridepublic void insert(User user) {System.out.println("在SQL Sever中给User表增加记录");}@Overridepublic User getUser(int id) {System.out.println("在SQL Sever中根据id获取User表的记录");return null;}}
AccessUser类
使用Access数据库时操作User:
public class AccessUser implements IUser {@Overridepublic void insert(User user) {System.out.println("在Access中给User表增加记录");}@Overridepublic User getUser(int id) {System.out.println("在Access中根据id获取User表的记录");return null;}}
工厂接口
所有数据库都得实现该工厂接口:
//工厂方法public interface IFactory {IUser createUser();}
SqlserverFactory类
SqlserverFactory实现工厂接口,用于使用Sqlserver进行访问:
public class SqlserverFactory implements IFactory {@Overridepublic IUser createUser() {return new SqlserverUser();}}
AccessFactory省略。
Client1
当需要更换数据库时,修改一条语句即可:
//工厂方法实现的客户端public class Client1 {public static void main(String[] args) {User user = new User();//换成AccessFactory()即可更换数据库IFactory factory = new SqlserverFactory();IUser iUser = factory.createUser();iUser.insert(user);iUser.getUser(1);}}
结果如下:
从上面可以看出,此时更换数据库直接替换一条语句即可,从而实现了业务与数据的解耦。但是如果除了User表,还有Department表呢?或者更多的呢?
抽象工厂模式
当有多个表、多个产品类时,普通的工厂方法模式就变成了抽象工厂模式。
添加Department类
public class Department {private int _id;private String departmentName;public int getId() {return _id;}public void setId(int id) {this._id = id;}public String getName() {return departmentName;}public void setName(String name) {this.departmentName = name;}}
IDepartment类、AccessDepartment类等省略,详见源码。
修改IFactory
需要修改IFactory类,添加department相关的代码
public interface IFactory {IUser createUser();IDepartment createDepartment();}
Client2
import com.whitedew.abstractfactory.impl.AccessFactory;//抽象工厂模式public class Client2 {public static void main(String[] args) {User user = new User();Department department = new Department();//SqlserverFactory();IFactory factory = new AccessFactory();IUser iUser = factory.createUser();iUser.insert(user);iUser.getUser(1);IDepartment iDepartment = factory.createDepartment();iDepartment.insert(department);iDepartment.getDepartment(1);}}
结果如下:
只有一个User类和User操作类的时候,是只需要工厂方法模式的,但如果有多个类并且又多个数据库操作时,工厂方法就不适用的。所以解决这种涉及到多个产品系列的问题,就需要使用抽象工厂方法。
抽象工厂+简单工厂
仔细分析上述代码,如果还需要增加一个业务表呢?比如Price表,需要做些什么?
增加三个类:IPrice、SqlserverPrice、AccessPrice;
修改三个地方:IFactory、SqlserverFactory、AccessFactory;
而且如果使用的数据库种类越多,需要增加和修改的地方就更多。
在这种情况下,可以使用简单工厂模式,将IFactory、SqlserverFactory、AccessFactory合并为一个类——DataAccess类:
public class DataAccess {//更换 Accessprivate static final String db = "Sqlserver";public static IUser CreateUser() {IUser result = null;switch (db) {case "Sqlserver":result = new SqlserverUser();break;case "Access":result = new AccessUser();break;}return result;}public static IDepartment CreateDepartment() {IDepartment result = null;switch (db) {case "Sqlserver":result = new SqlserverDepartment();break;case "Access":result = new AccessDepartment();break;}return result;}}
这时只需要在客户端调用DataAccess类即可:
//抽象工厂加简单工厂public class Client3 {public static void main(String[] args) {User user = new User();Department department = new Department();IUser iUser = DataAccess.CreateUser();iUser.insert(user);iUser.getUser(1);IDepartment iDepartment = DataAccess.CreateDepartment();iDepartment.insert(department);iDepartment.getDepartment(1);}}
使用抽象工厂+简单工厂,可以很大的简化代码,并且将修改和增加的地方都收敛至一处,一定程度上降低了开发的难度。
反射机制
在DataAccess中,如果需要更换数据库,需要手动修改,并且其中还有switch-case之类的语句:
通过反射机制可以解决这个问题。有关反射机制,简单来说,就是在程序运行的过程中,根据类属性,去动态地创建对应的类实例,而不需要像上面这样通过硬编码的方式来选择数据库。详情请见:链接
Java中反射可以这么实现,参考简说设计模式:
public class DataAccess {private static final String name = "com.whitedew.abstractfactory.impl";private static final String db = "Access";//要更换,换成Sqlserver即可public static IUser createUser() throws InstantiationException, IllegalAccessException, ClassNotFoundException {String className = name + "." + db + "User";return (IUser) Class.forName(className).newInstance();}public static IDepartment createDepartment() throws InstantiationException, IllegalAccessException, ClassNotFoundException {String className = name + "." + db + "Department";return (IDepartment) Class.forName(className).newInstance();}}
总结
现在我们来对三种工厂模式做一个总结:
- 简单工厂模式:通过接收的参数不同,来返回不同的对象实例,实现了客户端与具体业务对象的解耦;
- 工厂方法模式:在简单工厂模式的基础上进一步完善,符合开放-封闭原则;
- 抽象工厂模式:抽象工厂是应对多层业务耦合所出现的,典型的例子就是上述的数据与业务对象;
这三种模式在实际开发中非常实用,有关三者的取舍需要根据实际情况来确定,甚至有的时候需要进行重构。
