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


    一、什么是抽象工厂模式

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

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

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

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

        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同理。

     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与此类似。

     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 }

     二、抽象工厂模式的应用

        1. 何时使用

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

        2. 优点

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

        3. 缺点

    • 产品族扩展非常困难,改动或增加一个产品需同时改动多个类。

        4. 使用场景

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

        5. 应用实例

    • 生成不同操作系统的程序。
    • QQ换皮肤,一整套一起换。
    • 更换数据库。

    三、工厂方法模式的实现

           在看抽象工厂模式之前,我们先用工厂方法模式试一下。

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

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

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

    四、抽象工厂模式的实现

           上面用工厂方法模式实现了模拟更换数据库,但数据库中不可能只存在一个表,如果有多个表的情况又该如何呢?我们试着再增加一个表,比如增加一个部门表(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又是两个不同的分类,解决这种涉及到多个产品系列的问题,就用到了抽象工厂模式。

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

           在上述的两种模式中,我们是有多少个数据库就要创建多少个数据库工厂,而我们数据库工厂中的代码基本上是相同的,这时就可以使用简单工厂模式+抽象工厂模式来简化操作,也即将工厂类及工厂接口抛弃,取而代之的是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

  • 相关阅读:
    ppa 安装gnome3
    Ubuntu 升级VisualBox后无法启动 Kernel driver not installed (rc=-1908)
    ubuntu 安装 n卡 驱动
    apt-get 介绍。
    linux流量查看工具 iftop
    Ubuntu防火墙 UFW 设置
    Linux Shell快捷键(基本是通用的)
    gnome3 shell快捷键
    day2 -- 字符串常用方法、列表、字典
    day1 -- Python变量、注释、格式化输出字符串、input、if、while、for
  • 原文地址:https://www.cnblogs.com/adamjwh/p/9033552.html
Copyright © 2020-2023  润新知