抽象工厂模式(Abstract Factory),提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。UML结构图如下:
其中,AbstractFactory是抽象工厂接口,里面包含所有的产品创建的抽象方法;ConcreteFactory则是具体的工厂,创建具有特定实现的产品对象;AbstractProduct是抽象产品,有可能由两种不同的实现;ConcreteProduct则是对于抽象产品的具体分类的实现。
抽象工厂模式是工厂方法模式的升级版本,在有多个业务品种、业务分类时,通过抽象工厂模式产生需要的对象是一种非常好的解决方式。下面是抽象工厂模式的通用源代码类图:
下述代码是一个抽象工厂类,它的职责是定义每个工厂要实现的功能,有n个产品族,在抽象工厂类中就应该有n个创建方法。这里按上述类图,给出A、B两个产品族,即构造两个方法。
1 public abstract class AbstractFactory { 2 3 //创建A产品家族 4 public abstract AbstractProductA createProductA(); 5 //创建B产品家族 6 public abstract AbstractProductB createProductB(); 7 8 }
抽象产品类,两个抽象产品类可以有关系,例如共同继承或实现一个抽象类或接口。这里给出A产品的抽象类,产品类B类似,不再赘述。
1 public abstract class AbstractProductA { 2 3 //每个产品共有的方法 4 public void shareMethod() {} 5 //每个产品相同方法,不同实现 6 public abstract void doSomething(); 7 8 }
具体工厂实现类,如何创建一个产品是由具体的实现类来完成的。下方给出产品等级1的实现类,等级2同理。
1 public class ConcreteFactory1 extends AbstractFactory { 2 3 @Override 4 public AbstractProductA createProductA() { 5 return new ProductA1(); 6 } 7 8 @Override 9 public AbstractProductB createProductB() { 10 return new ProductB1(); 11 } 12 13 }
两个具体产品的实现类,这里只给出A的两个具体产品类,B与此类似。
1 public class ProductA1 extends AbstractProductA { 2 3 @Override 4 public void doSomething() { 5 System.out.println("产品A1实现方法"); 6 } 7 8 }
1 public class ProductA2 extends AbstractProductA { 2 3 @Override 4 public void doSomething() { 5 System.out.println("产品A2实现方法"); 6 } 7 8 }
1 public class Client { 2 3 public static void main(String[] args) { 4 //定义两个工厂 5 AbstractFactory factory1 = new ConcreteFactory1(); 6 AbstractFactory factory2 = new ConcreteFactory2(); 7 8 //产生A1对象 9 AbstractProductA a1 = new ProductA1(); 10 //产生A2对象 11 AbstractProductA a2 = new ProductA2(); 12 //产生B1对象 13 AbstractProductB b1 = new ProductB1(); 14 //产生B2对象 15 AbstractProductB b2 = new ProductB2(); 16 17 //.... 18 } 19 20 }
在看抽象工厂模式之前,我们先用工厂方法模式试一下。
以模拟更换数据库为例,UML图如下:
这里我们先只对User表进行操作,所以工厂方法只有CreateUser()。
定义一个创建访问User表对象的抽象的工厂接口。
1 public interface IFactory { 2 3 IUser createUser(); 4 5 }
用于客户端访问,解除与具体数据库访问的耦合。模拟插入方法insert。
1 public interface IUser { 2 3 public void insert(User user); 4 public User getUser(int id); 5 6 }
用于访问SQLServer的User。
1 public class SqlserverUser implements IUser { 2 3 @Override 4 public void insert(User user) { 5 System.out.println("insert info into user with sqlserver"); 6 } 7 8 @Override 9 public User getUser(int id) { 10 System.out.println("get info from user by id with sqlserver"); 11 return null; 12 } 13 14 }
用于访问Access的User。
1 public class AccessUser implements IUser { 2 3 @Override 4 public void insert(User user) { 5 System.out.println("insert info into user with access"); 6 } 7 8 @Override 9 public User getUser(int id) { 10 System.out.println("get info from user by id with access"); 11 return null; 12 } 13 14 }
实现IFactory接口,实例化SqlserverFactory。
1 public class SqlserverFactory implements IFactory { 2 3 @Override 4 public IUser createUser() { 5 return new SqlserverUser(); 6 } 7 8 }
实现IFactory接口,实例化AccessFactory。
1 public class AccessFactory implements IFactory { 2 3 @Override 4 public IUser createUser() { 5 return new AccessUser(); 6 } 7 8 }
1 public class Client { 2 3 public static void main(String[] args) { 4 User user = new User(); 5 6 IFactory factory = new SqlserverFactory(); 7 // IFactory factory = new AccessFactory(); 8 9 IUser iUser = factory.createUser(); 10 iUser.insert(user); 11 iUser.getUser(1); 12 } 13 14 }
这就是工厂方法模式的实现,现在只需把new SqlserverFactory()改成下方注释那样的new AccessFactory()就可以实现更换数据库了,此时由于多态的关系,使得声明IUser接口的对象iUser事先根本不知道是在访问哪个数据库,却可以在运行时很好地完成工作,这就是所谓的业务逻辑与数据访问的解耦。
具体工厂方法模式可参考之前的文章:。
上面用工厂方法模式实现了模拟更换数据库,但数据库中不可能只存在一个表,如果有多个表的情况又该如何呢?我们试着再增加一个表,比如增加一个部门表(Department表)。UML图如下:
先更改一下IFactory接口,增加一个创建访问Department表对象的抽象的工厂接口。
1 public interface IFactory { 2 3 IUser createUser(); 4 IDepartment createDepartment(); 5 6 }
增加一个IDepartment接口,用于客户端访问。
1 public interface IDepartment { 2 3 public void insert(Department department); 4 public Department getDepartment(int id); 5 6 }
在sqlserver数据库工厂中实例化SqlserverDepartment,Access同理。
1 public class SqlserverFactory implements IFactory { 2 3 @Override 4 public IUser createUser() { 5 return new SqlserverUser(); 6 } 7 8 @Override 9 public IDepartment createDepartment() { 10 return new SqlserverDepartment(); 11 } 12 13 }
增加SqlserverDepartment及AccessDepartment。
1 public class SqlserverDepartment implements IDepartment { 2 3 @Override 4 public void insert(Department department) { 5 System.out.println("insert info into department with sqlserver"); 6 } 7 8 @Override 9 public Department getDepartment(int id) { 10 System.out.println("get info from department by id with sqlserver"); 11 return null; 12 } 13 14 }
在客户端中增加department的实现。
1 public class Client { 2 3 public static void main(String[] args) { 4 User user = new User(); 5 Department department = new Department(); 6 7 IFactory factory = new SqlserverFactory(); 8 // IFactory factory = new AccessFactory(); 9 10 IUser iUser = factory.createUser(); 11 iUser.insert(user); 12 iUser.getUser(1); 13 14 IDepartment iDepartment = factory.createDepartment(); 15 iDepartment.insert(department); 16 iDepartment.getDepartment(1); 17 } 18 19 }
如上述代码,运行sqlserver数据库的结果如下:
若更换为access数据库,运行结果如下:
刚才我们只有一个User类和User操作类的时候,只需要工厂方法模式即可,但现在显然数据库中有许多的表,而SQLServer和Access又是两个不同的分类,解决这种涉及到多个产品系列的问题,就用到了抽象工厂模式。
在上述的两种模式中,我们是有多少个数据库就要创建多少个数据库工厂,而我们数据库工厂中的代码基本上是相同的,这时就可以使用简单工厂模式+抽象工厂模式来简化操作,也即将工厂类及工厂接口抛弃,取而代之的是DataAccess类,由于实现设置了db的值(Sqlserver或Access),所以简单工厂的方法都不需要输入参数,这样客户端可以直接生成具体的数据库访问类实例,且没有出现任何一个SQLServer或Access的字样,达到解耦的目的。可以看一下UML图:
但此时还有一个问题就是,因为DataAccess中创建实例的过程使用的是switch语句,所以如果此时要增加一个数据库,比如Oracle数据库,就需要修改每个switch的case了,违背了开闭原则。对于这种情况,工厂方法模式里有提到过,就是使用反射机制,或者这里应该更确切的说是依赖注入(DI),spring的IoC中有遇到这个概念。
这里我们可以直接使用反射来利用字符串去实例化对象,这样变量就是可更换的了,换句话说就是将程序由编译时转为运行时,如下:
1 public class DataAccess { 2 3 private static final String name = "com.adamjwh.gofex.abstract_factory"; 4 private static final String db = "Access"; 5 6 public static IUser createUser() throws InstantiationException, IllegalAccessException, ClassNotFoundException { 7 String className = name + "." + db + "User"; 8 return (IUser) Class.forName(className).newInstance(); 9 } 10 11 public static IDepartment createDepartment() throws InstantiationException, IllegalAccessException, ClassNotFoundException { 12 String className = name + "." + db + "Department"; 13 return (IDepartment) Class.forName(className).newInstance(); 14 } 15 16 }
这里字符串name为包名,db为要查询的数据库名,我们要更换数据库只需将Access修改成我们需要的数据库名即可,这时只需修改DataAccess类即可,然后我们再在Client中对DataAccess类实例化。
DataAccess factory = new DataAccess();
当然我们还可以利用配置文件来解决更改DataAccess的问题,此时就连DataAccess类都不用更改了,如下:
1 <?xml version="1.0 encoding="utf-8" ?> 2 <configuration> 3 <appSettings> 4 <add key="DB" value="Oracle" /> 5 </appSettings> 6 </configuration>
所以,所有在用简单工厂的地方,都可以考虑用反射技术来去除swithc或if,解除分支判断带来的耦合。
如对本文有疑问, 点击进行留言回复!!
【面试题】研究过tomcat的NioEndpoint源码吗?请阐述下Reactor多线程模型在tomcat中的实现。
荐 厉害了!阿里P8架构师用4大技术文档带你深入解读爆火的中台战略
网友评论