抽象工厂模式( Abstract Factory),提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
抽象工厂模式( Abstract Factory),提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。 参考:
工厂方法中每一个工厂只提供一种产品,为此,抽象工厂将工厂方法中的所用工厂向上抽象出一个抽象工厂,将所有产品向上抽象出抽象产品,这样抽象工厂模式中的角色就包括:
对比三种工厂模式,简单工厂直接在一个静态工厂中返回产品实例,没有对产品或工厂做任何抽象,是最简单粗暴的工厂模式,但每次添加新的产品都要修改工厂类,违反开闭原则,后来工厂方法将工厂类向上抽象出一个“抽象工厂接口”,让每一个具体的工厂类只返回一种产品,这样添加新产品时只需要给“抽象工厂接口”添加新的实现类即可,但工厂方法的问题在于每个具体工厂只能生产一种产品,因此,在抽象工厂中,把一系列产品进一步分装为抽象产品,具体工厂就可以产生产生多个产品了。
当抽象工厂模式中每一个具体工厂类只创建一个产品对象,也就是只存在一个产品等级结构时,抽象工厂模式退化成工厂方法模式;当工厂方法模式中抽象工厂与具体工厂合并,提供一个统一的工厂来创建产品对象,并将创建对象的工厂方法设计为静态方法时,工厂方法模式退化成简单工厂模式。
模拟在 MySQL 或 SQLServer 数据库中对用户信息(User)和部门信息(Department)的查询(select)和插入(insert)功能。(假设MySQL 和 SQLServer 中的查询和插入语句不同),同时希望客户端对数据库的变更不敏感。
要实现的功能是在 MySQL 或 SQL Server 中实现对 User 和 Department 的插入和查询,把 SQL Server 的插入和查询看作 MySQL 插入查询的变体,那么就可以把系统功能抽象为对 User 的操作和对 Department 的操作,分别建立两个接口表示:
public interface IUserDao {
void insert(User user);
void select(String name);
}
public interface IDepartDao {
Department select(String name);
void insert(Department department);
}
由于 MySQL 和 SQLServer 中的查询和插入语句不同, 所以他们分别实现这两个接口,表示两种数据库具体的操作细节。
public class MySQLUserDao implements IUserDao {
@Override
public void insert(User user) {
System.out.println("使用MySQL的插入方法把User插入表中");
}
@Override
public void select(String name) {
System.out.println("使用MySQL中的查询方法查询name");
}
}
public class MySQLDepartDao implements IDepartDao {
// ......
}
public class SQLServerUserDao implements IUserDao {
// ......
}
public class SQLServerDepartDao implements IDepartDao {
// ......
}
然后就需要一个工厂来产生具体的用来操作数据的对象,如果采用工厂方法,就要建立四个工厂类,产品更多时,显然不合适,但其实MySQLUserDao
和MySQLDepartDao
可以看作是一类对象(MySQL处理数据的对象),SQLServerUserDao
和SQLServerDepartDao
可以看作是一类对象(SQL Server处理数据的对象),这样只需要建立两个工厂类就可以了,这两个工厂类又可以向上抽象(他们都是返回一个IUserDao
对象, 一个IDepartDao
)对象,所以最终为:
public interface IFactor {
IUserDao getUserDao();
IDepartDao getDepartDao();
}
public class MySQLFactory implements IFactor {
@Override
public IDepartDao getDepartDao() {
return new MySQLDepartDao();
}
@Override
public IUserDao getUserDao() {
return new MySQLUserDao();
}
}
public class SQLServerFactory implements IFactor {
@Override
public IDepartDao getDepartDao() {
return new SQLServerDepartDao();
}
@Override
public IUserDao getUserDao() {
return new SQLServerUserDao();
}
}
客户端代码:
public class Client {
public static void main(String[] args) {
IFactor factor = new MySQLFactory();
IUserDao userDao = factor.getUserDao();
IDepartDao departDao = factor.getDepartDao();
userDao.insert(new User("1", "张三"));
userDao.select("张三");
}
}
如果抽象工厂中加入了新的产品,那首先必须改变抽象工厂接口,其次所有的具体工厂类也要跟着修改,为此可以选择放弃工厂方法的思想,改用简单工厂的思想。
public class DaoFactory {
private static String db = "MySQL";
// private static String db = "SQLServer";
public static IUserDao getUserDao(){
switch (db){
case "MySQL":
return new MySQLUserDao();
case "SQLServer":
return new SQLServerUserDao();
default:
return null;
}
}
public static IDepartDao getDepartDao(){
switch (db){
case "MySQL":
return new MySQLDepartDao();
case "SQLServer":
return new SQLServerDepartDao();
default:
return null;
}
}
}
放弃抽象工厂接口以及具体工厂类后,如果要添加新产品,只要修改DaoFactory
一个类就可以了,并且由于在DaoFactory
中定义了要使用的数据库的类型(变体),换数据库时,就只需要改变DaoFactory
就可以了,进一步降低了与客户端的耦合度。但简单工厂“从工厂内看”不符合开闭原则,如果要新添加一个数据库(新添加一种变体)如Oricle, 使用工厂方法时只需要添加一个抽象工厂接口的实现类(满足开闭原则),现在需要修改DaoFactory
(不符合开闭原则),为此可以使用反射。
package pers.junebao.abstract_factory.db;
public class DaoFactory {
private static String db = "MySQL";
// private static String db = "SQLServer";
public static IUserDao getUserDao(){
try {
try {
return (IUserDao) Class.forName("pers.junebao.abstract_factory.db.SQLServer" + db + "UserDao").newInstance();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
return null;
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
return null;
}
}
public static IDepartDao getDepartDao(){
try {
try {
return (IDepartDao) Class.forName("pers.junebao.abstract_factory.db." + db + "DepartDao").newInstance();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
return null;
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
return null;
}
}
}
完整类名,选择的数据库名这些都可以放到配置文件中,进一步降低修改成本
适用场景:
优点:
缺点: