简说设计模式——抽象工厂模式

1、什么是抽象工厂模式

       抽象工厂模式其实就是多个工厂方法模式,好比前面工厂方法模式中,咱们建立多个不一样类型的数据库,有MySQL、SQLServer等等,就是用工厂方法模式来实现的,但此时咱们只能实现一个表(具体内容见下方工厂模式的实现),咱们数据库中固然不可能只有一个表呀,因此抽象工厂模式就来了。html

  抽象工厂模式(Abstract Factory),提供一个建立一系列相关或相互依赖对象的接口,而无需指定它们具体的类。UML结构图以下:git

       其中,AbstractFactory是抽象工厂接口,里面包含全部的产品建立的抽象方法;ConcreteFactory则是具体的工厂,建立具备特定实现的产品对象;AbstractProduct是抽象产品,有可能由两种不一样的实现;ConcreteProduct则是对于抽象产品的具体分类的实现。spring

       抽象工厂模式是工厂方法模式的升级版本,在有多个业务品种、业务分类时,经过抽象工厂模式产生须要的对象是一种很是好的解决方式。下面是抽象工厂模式的通用源代码类图:sql

    1. AbstractFactory类

       下述代码是一个抽象工厂类,它的职责是定义每一个工厂要实现的功能,有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 }

    2. AbstractProduct类

       抽象产品类,两个抽象产品类能够有关系,例如共同继承或实现一个抽象类或接口。这里给出A产品的抽象类,产品类B相似,再也不赘述。设计模式

1 public abstract class AbstractProductA {
2 
3     //每一个产品共有的方法
4     public void shareMethod() {}
5     //每一个产品相同方法,不一样实现
6     public abstract void doSomething();
7     
8 }

    3. ConcreteFactory类

       具体工厂实现类,如何建立一个产品是由具体的实现类来完成的。下方给出产品等级1的实现类,等级2同理。markdown

 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 }

    4. ConcreteProduct类

        两个具体产品的实现类,这里只给出A的两个具体产品类,B与此相似。app

 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  }

  5. Client客户端

       在客户端中,没有任何一个方法与实现类有关系,对于一个产品来讲,咱们只须要知道它的工厂方法就能够直接产生一个产品对象,不必关心他的实现类。
 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 }

 2、抽象工厂模式的应用

    1. 什么时候使用

  • 系统的产品有多于一个的产品族,而系统只消费其中某一族的产品时。

    2. 优势

  • 封装性,易于产品交换。因为具体工厂类在一个应用中只需在初始化的时候出现一次,这就使得改变一个应用的具体工厂变得很是容易,只需改变具体工程便可使用不一样的产品配置。
  • 建立实例过程与客户端分离。

    3. 缺点

  • 产品族扩展很是困难,改动或增长一个产品需同时改动多个类。

    4. 使用场景

  • 一个对象族(或一组没有任何关系的对象)都有相同的约束。

    5. 应用实例

  • 生成不一样操做系统的程序。
  • QQ换皮肤,一整套一块儿换。
  • 更换数据库。

3、工厂方法模式的实现

       在看抽象工厂模式以前,咱们先用工厂方法模式试一下。ide

       以模拟更换数据库为例,UML图以下:sqlserver

       这里咱们先只对User表进行操做,因此工厂方法只有CreateUser()。

    1. IFactory接口

       定义一个建立访问User表对象的抽象的工厂接口。

1 public interface IFactory {
2 
3     IUser createUser();
4     
5 }

    2. IUser接口

       用于客户端访问,解除与具体数据库访问的耦合。模拟插入方法insert。

1 public interface IUser {
2     
3     public void insert(User user);
4     public User getUser(int id);
5 
6 }

    3. SqlserverUser类

       用于访问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 }

    4. AccessUser类

       用于访问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 }

    5. SqlserverFactory类

       实现IFactory接口,实例化SqlserverFactory。

1 public class SqlserverFactory implements IFactory {
2 
3     @Override
4     public IUser createUser() {
5         return new SqlserverUser();
6     }
7 
8 }

    6. AccessFactory类

       实现IFactory接口,实例化AccessFactory。

1 public class AccessFactory implements IFactory {
2 
3     @Override
4     public IUser createUser() {
5         return new AccessUser();
6     }
7 
8 }

    7. Client客户端

 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事先根本不知道是在访问哪一个数据库,却能够在运行时很好地完成工做,这就是所谓的业务逻辑与数据访问的解耦。

       具体工厂方法模式可参考以前的文章:简说设计模式——工厂方法模式

4、抽象工厂模式的实现

       上面用工厂方法模式实现了模拟更换数据库,但数据库中不可能只存在一个表,若是有多个表的状况又该如何呢?咱们试着再增长一个表,好比增长一个部门表(Department表)。UML图以下:

    1. IFactory接口

       先更改一下IFactory接口,增长一个建立访问Department表对象的抽象的工厂接口。

1 public interface IFactory {
2 
3     IUser createUser();
4     IDepartment createDepartment();
5     
6 }

    2. IDepartment接口

       增长一个IDepartment接口,用于客户端访问。

1 public interface IDepartment {
2     
3     public void insert(Department department);
4     public Department getDepartment(int id);
5 
6 }

    3. 数据库工厂

       在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 }

    4. IDepartment接口的实现类

       增长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 }

    5. Client客户端

       在客户端中增长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又是两个不一样的分类,解决这种涉及到多个产品系列的问题,就用到了抽象工厂模式。

5、利用反射实现数据访问程序

       在上述的两种模式中,咱们是有多少个数据库就要建立多少个数据库工厂,而咱们数据库工厂中的代码基本上是相同的,这时就可使用简单工厂模式+抽象工厂模式来简化操做,也即将工厂类及工厂接口抛弃,取而代之的是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,解除分支判断带来的耦合。

 

       源码地址:https://gitee.com/adamjiangwh/GoF

相关文章
相关标签/搜索