0、关于这三个模式

一般情况下,工厂模式分为三种更加细分的类型:简单工厂、工厂方法和抽象工厂。不过,在 GoF 的《设计模式》一书中,它将简单工厂模式看作是工厂方法模式的一种特例,所以工厂模式只被分成了工厂方法和抽象工厂两类。实际上,前面一种分类方法更加常见,所以,在本文的讲解中,我沿用第一种分类方法。

在这三种细分的工厂模式中,简单工厂、工厂方法原理比较简单,在实际的项目中也比较常用。而抽象工厂的原理稍微复杂点,在实际的项目中相对也不常用。所以,我们今天讲解的重点是前两种工厂模式。对于抽象工厂,你稍微了解一下即可。

学习设计模式的重点也不是原理和实现,因为这些都很简单,重点还是需要你在今后的学习中搞清楚应用场景,什么时候用什么,这样做有什么好处,有没有其他办法能实现。

1、什么是

1.1、什么是简单工厂模式?

1)根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类
2)在简单工厂模式中用于被创建实例的方法通常为静态(static)方法,因此简单工厂模式又被称为静态工厂方法(Static Factory Method)

1.2、什么是工厂模式?

简单工厂模式存在 3 个问题:
1)工厂类集中了所有实例(产品)的创建逻辑,一旦这个工厂不能正常工作,整个系统都会受到影响;
2)违背“开放 - 关闭原则”,一旦添加新产品就不得不修改工厂类的逻辑,这样就会造成工厂逻辑过于复杂。
3)简单工厂模式由于使用了静态工厂方法,静态方法不能被继承和重写,会造成工厂角色无法形成基于继承的等级结构。

为了解决上述的问题,我们又使用了一种新的设计模式:工厂模式。

工厂方法模式,又称工厂模式、多态工厂模式和虚拟构造器模式,通过定义工厂父类负责定义创建对象的公共接口,而子类则负责生成具体的对象。它将类的实例化(具体产品的创建)延迟到工厂类的子类(具体工厂)中完成,即由子类来决定应该实例化(创建)哪一个类。

1.3、什么是抽象工厂模式?

前面介绍的工厂方法模式中考虑的是一类产品的生产,如畜牧场只养动物、电视机厂只生产电视机、计算机软件学院只培养计算机软件专业的学生等。同种类称为同等级,也就是说:工厂方法模式只考虑生产同等级的产品,但是在现实生活中许多工厂是综合型的工厂,能生产多等级(种类) 的产品,如农场里既养动物又种植物,电器厂既生产电视机又生产洗衣机或空调,大学既有软件专业又有生物专业等。

抽象工厂(AbstractFactory)模式是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。

抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。使用抽象工厂模式一般要满足以下条件。

  • 系统中有多个产品族,每个具体工厂创建同一族但属于不同等级结构的产品。
  • 系统一次只可能消费其中某一族产品,即同族的产品一起使用。

抽象工厂模式除了具有工厂方法模式的优点外,其他主要优点如下。

  • 可以在类的内部对产品族中相关联的多等级产品共同管理,而不必专门引入多个新的类来进行管理。
  • 当需要产品族时,抽象工厂可以保证客户端始终只使用同一个产品的产品组。
  • 抽象工厂增强了程序的可扩展性,当增加一个新的产品族时,不需要修改原代码,满足开闭原则。

其缺点是:当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。增加了系统的抽象性和理解难度。

2、为什么要

2.1、为什么要使用简单工厂模式

2.2、为什么要使用工厂模式

2.3、为什么要使用抽象工厂模式

3、例子

4、总结

前言

小A今天去面试,面试官出了这样一道面试题:

使用任意一种面向对象语言实现一个计算器控制台程序,要求输入两个数和运算符号,得到结果。

PS:为了接下来阅读方便,我将两个操作数和运算符号写死在代码中,就不做成控制台输入了。

version1(面向过程编程)

小A一想,不到5分钟就给出了 version1 。

  1. public class Main {
  2. public static void main(String[] args) {
  3. double result;
  4. double numberA = 10;
  5. double numberB = 20;
  6. // char operate = '+';
  7. // char operate = '-';
  8. // char operate = '*';
  9. char operate = '/';
  10. if (operate == '+') {
  11. result = numberA + numberB;
  12. System.out.println("两数相加:" + numberA + " + " + numberB + " = " + result);
  13. } else if (operate == '-') {
  14. result = numberA - numberB;
  15. System.out.println("两数相减:" + numberA + " - " + numberB + " = " + result);
  16. } else if (operate == '*') {
  17. result = numberA * numberB;
  18. System.out.println("两数相乘:" + numberA + " * " + numberB + " = " + result);
  19. } else if (operate == '/') {
  20. result = numberA / numberB;
  21. System.out.println("两数相除:" + numberA + " / " + numberB + " = " + result);
  22. } else {
  23. System.out.println("暂不支持此运算");
  24. }
  25. }
  26. }

image.png
且先不说出题人的意思,这个 version1 现在的代码,就有很多不足的地方需要改进。

问题:

  1. 使用 if - else 分支判断,意味着每个条件都要做判断,等于计算机做了三次无用功。(改用 switch)
  2. 如果除数是0,或者输入的是特殊符号不是数字怎么办?(异常捕获)

即便修正了以上的两个问题,这样的代码也是不符合出题人意思的。

几乎所有编程初学者都会有这样的问题,就是碰到问题就直觉地用计算机能够理解的逻辑来描述和表达待解决的问题及具体的求解过程。这其实是用计算机的方式去思考,比如计算器这个程序,先要求输入两个数和运算符号,然后根据运算符号判断选择如何运算,得到结果,这本身没有错,但这样的思维却使得我们的程序只为满足实现当前的需求,程序不容易维护,不容易扩展,更不容易复用。从而达不到高质量代码的要求。

version2(业务逻辑封装)

为了提高程序的可复用性,我们可以把计算逻辑单独封装成一个类,这样一来,只要有用到计算的,都可以复用这个类,提高开发效率。

  1. public class Main {
  2. public static void main(String[] args) {
  3. double result = 0;
  4. double numberA = 10;
  5. double numberB = 20;
  6. // char operate = '+';
  7. // char operate = '-';
  8. // char operate = '*';
  9. char operate = '/';
  10. // char operate = '$';
  11. result = CalculateServices.calculate(numberA, numberB, operate);
  12. System.out.println("result: " + result);
  13. }
  14. }
  15. /**
  16. * 将计算逻辑封装到类中
  17. */
  18. class CalculateServices {
  19. public static double calculate(double numberA, double numberB, char operate) {
  20. double result = 0;
  21. switch (operate) {
  22. case '+':
  23. result = numberA + numberB;
  24. System.out.println("两数相加:" + numberA + " + " + numberB + " = " + result);
  25. break;
  26. case '-':
  27. result = numberA - numberB;
  28. System.out.println("两数相减:" + numberA + " - " + numberB + " = " + result);
  29. break;
  30. case '*':
  31. result = numberA * numberB;
  32. System.out.println("两数相乘:" + numberA + " * " + numberB + " = " + result);
  33. break;
  34. case '/':
  35. if (numberA == 0 || numberB == 0) {
  36. System.out.println("number 不能为0");
  37. break;
  38. }
  39. result = (double) numberA / numberB;
  40. System.out.println("两数相除:" + numberA + " / " + numberB + " = " + result);
  41. break;
  42. default:
  43. System.out.println("暂不支持此运算");
  44. }
  45. return result;
  46. }
  47. }

1、简单工厂模式(version3 解耦)

如果出题人希望增加一个开根(sqrt)运算,那就要在 CalculateServices 类的 switch 中加一个分支。

问题是现在要加一个平方根运算,却需要让加减乘除的运算都得来参与编译,如果一不小心,把加法运算改成了减法,这岂不是大大的糟糕。

本来是让你加一个功能,却使得原有的运行良好的功能代码产生了变化,这个风险太大了。

应该把加减乘除等运算分离,修改其中一个不影响另外的几个,这样增加运算算法也不影响其他代码。

到底要实例化哪个计算类,将来会不会增加实例化的对象,比如增加开根运算,这是很容易变化的地方,应该考虑用一个单独的类来做这个创造实例的过程,这就是工厂。

public class Main {
    public static void main(String[] args) {
        double result = 0;
        double numberA = 10;
        double numberB = 20;

//        String operate = "+";
//        String operate = "-";
//        String operate = "*";
        String operate = "/";
//        String operate = "$";

        CalculateServices calculateServices = SimpleCalculateServicesFactory.createService(operate);

        result = calculateServices.calculate(numberA, numberB);
        System.out.println("result: " + result);
    }
}

/**
 * 将计算逻辑封装到类中
 */
abstract class CalculateServices {
    public abstract double calculate(double numberA, double numberB);
}

/**
 * 加法类
 */
class CalculateAddServices extends CalculateServices {
    @Override
    public double calculate(double numberA, double numberB) {
        return numberA + numberB;
    }
}

/**
 * 减法类
 */
class CalculateSubServices extends CalculateServices {
    @Override
    public double calculate(double numberA, double numberB) {
        double result = numberA - numberB;
        System.out.println("两数相加:" + numberA + " + " + numberB + " = " + result);
        return result;
    }
}

/**
 * 乘法类
 */
class CalculateMulServices extends CalculateServices {
    @Override
    public double calculate(double numberA, double numberB) {
        double result = numberA * numberB;
        System.out.println("两乘相加:" + numberA + " * " + numberB + " = " + result);
        return result;
    }
}

/**
 * 除法类
 */
class CalculateDivServices extends CalculateServices {
    @Override
    public double calculate(double numberA, double numberB) {
        double result = numberA / numberB;
        System.out.println("两数相除:" + numberA + " / " + numberB + " = " + result);
        return result;
    }
}

/**
 * 简单运算工厂类
 */
class SimpleCalculateServicesFactory {
    public static CalculateServices createService(String operate) {
        CalculateServices calculateServices = null;
        switch (operate) {
            case "+":
                calculateServices = new CalculateAddServices();
                break;
            case "-":
                calculateServices = new CalculateSubServices();
                break;
            case "*":
                calculateServices = new CalculateMulServices();
                break;
            case "/":
                calculateServices = new CalculateDivServices();
                break;
            default:
                System.out.println("暂不支持此运算");
        }
        return calculateServices;
    }
}

image.png
image.png

只需要输入运算符号,工厂就实例化出合适的对象,通过多态,返回父类的方式实现了计算器的结果。

如果有一天我们需要更改加法运算,我们只需要改 CalculateAddServices 就可以了。

如果需要增加各种复杂运算,比如平方根,立方根,自然对数,正弦余弦等,如何做?
只要增加相应的运算子类,并修改运算类工厂,在 switch 中增加分支。

简单工厂模式的最大优点在于工厂类中包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关的类,对于客户端来说,去除了与具体产品的依赖。 就像你的计算器,让客户端不用管该用哪个类的实例,只需要把‘+’给工厂,工厂自动就给出了相应的实例,客户端只要去做运算就可以了,不同的实例会实现不同的运算。

但问题也就在这里,如你所说,如果要加各种复杂运算,我们是一定需要修改原有的类的,也就是给运算工厂类的方法里加‘Case’的分支条件。

这可不是好办法,这就等于说,我们不但对扩展开放了,对修改也开放了,这样就违背了开闭原则(开放-封闭原则),于是工厂方法就来了。

2、工厂模式(version4 进一步解耦)

以下是工厂模式的实现代码:

public class Main {
    public static void main(String[] args) {
        double result = 0;
        double numberA = 10;
        double numberB = 20;

//        String operate = "+";
//        String operate = "-";
        String operate = "*";
//        String operate = "/";
//        String operate = "$";

        CalculateServicesFactory factory = null;
        switch (operate) {
            case "+":
                factory = new CalculateAddServicesFactory();
                break;
            case "-":
                factory = new CalculateSubServicesFactory();
                break;
            case "*":
                factory = new CalculateMulServicesFactory();
                break;
            case "/":
                factory = new CalculateDivServicesFactory();
                break;
            default:
                System.out.println("暂不支持此运算");
                return;
        }

        CalculateServices services = factory.createCalculateServices();

        result = services.calculate(numberA, numberB);
        System.out.println("result: " + result);
    }
}

/**
 * 将每个计算逻辑封装到各自的类中
 */
abstract class CalculateServices {
    public abstract double calculate(double numberA, double numberB);
}

/**
 * 加法类
 */
class CalculateAddServices extends CalculateServices {
    @Override
    public double calculate(double numberA, double numberB) {
        double result = numberA + numberB;
        System.out.println("两数相加:" + numberA + " + " + numberB + " = " + result);
        return result;
    }
}

/**
 * 减法类
 */
class CalculateSubServices extends CalculateServices {
    @Override
    public double calculate(double numberA, double numberB) {
        double result = numberA - numberB;
        System.out.println("两数相减:" + numberA + " + " + numberB + " = " + result);
        return result;
    }
}

/**
 * 乘法类
 */
class CalculateMulServices extends CalculateServices {
    @Override
    public double calculate(double numberA, double numberB) {
        double result = numberA * numberB;
        System.out.println("两乘相乘:" + numberA + " * " + numberB + " = " + result);
        return result;
    }
}

/**
 * 除法类
 */
class CalculateDivServices extends CalculateServices {
    @Override
    public double calculate(double numberA, double numberB) {
        double result = numberA / numberB;
        System.out.println("两数相除:" + numberA + " / " + numberB + " = " + result);
        return result;
    }
}


/**
 * 工厂接口,然后加减乘除各建一个具体工厂去实现这个接口
 */
interface CalculateServicesFactory {
    CalculateServices createCalculateServices();
}

/**
 * 加法工厂
 */
class CalculateAddServicesFactory implements CalculateServicesFactory {
    @Override
    public CalculateServices createCalculateServices() {
        return new CalculateAddServices();
    }
}

/**
 * 减法工厂
 */
class CalculateSubServicesFactory implements CalculateServicesFactory {
    @Override
    public CalculateServices createCalculateServices() {
        return new CalculateSubServices();
    }
}

/**
 * 乘法工厂
 */
class CalculateMulServicesFactory implements CalculateServicesFactory {
    @Override
    public CalculateServices createCalculateServices() {
        return new CalculateMulServices();
    }
}

/**
 * 除法工厂
 */
class CalculateDivServicesFactory implements CalculateServicesFactory {
    @Override
    public CalculateServices createCalculateServices() {
        return new CalculateDivServices();
    }
}

核心

CalculateServicesFactory factory = new CalculateAddServicesFactory();
CalculateServices services = factory.createCalculateServices();
result = services.calculate(numberA, numberB);

结构
image.png

看完以上的实现代码,初学者可能会有下面的这种疑惑:
以前我们不是说过,如果我现在需要增加其他复杂运算,比如求M数的N次方,或者求M数的N次方根,这些功能的增加,在简单工厂里,我是先去加‘求M数的N次方’功能类,然后去更改工厂方法,当中加‘Case’语句来做判断,现在用了工厂方法,加功能类没问题,再加相关的工厂类,这也没问题,但要我再去更改客户端,这不等于不但没有减化难度,反而增加了很多类和方法,把复杂性增加了吗?

简单工厂模式的工厂类与分支耦合,那么我就对它下手,根据依赖倒转原则,我们把工厂类抽象出一个接口,这个接口只有一个方法,就是创建抽象产品的工厂方法。然后,所有的要生产具体类的工厂,就去实现这个接口,这样,一个简单工厂模式的工厂类,变成了一个工厂抽象接口和多个具体生成对象的工厂,于是我们要增加‘求M数的N次方’的功能时,就不需要更改原有的工厂类了,只需要增加此功能的运算类和相应的工厂类就可以了。

虽然简单工厂模式的结构比较简单,易于理解,但它违反了开闭原则,而工厂模式解决了这个问题。它克服了简单工厂违背开放-封闭原则的缺点,又保持了封装对象创建过程的优点。

其实你仔细观察就会发现,工厂方法模式实现时,客户端需要决定实例化哪一个工厂来实现运算类,选择判断的问题还是存在的,也就是说,工厂方法把简单工厂的内部逻辑判断移到了客户端代码来进行。你想要加功能,本来是改工厂类的,而现在是修改客户端。

不过这个问题以后再说。

简单工厂模式和工厂模式它们都是集中封装了对象的创建,使得要更换对象时,不需要做大的改动就可实现,降低了客户程序与产品对象的耦合。工厂方法模式是简单工厂模式的进一步抽象和推广。由于使用了多态性,工
厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。但缺点是由于每加一个产品,就需要加一个产品工厂的类,增加了额外的开发量。

利用‘反射’可以解决避免分支判断的问题。

不过今天还是不急,等以后再谈。

3、抽象工厂模式

为了讲明白抽象工厂模式,我们得换一个案例。

以更换 DB 类型的场景为例:
本来小菜写好了一个项目,是给一家企业做的电子商务网站,是用 SQL Server 作为数据库的,应该说上线后除了开始有些小问题,基本都还可以。而后,公司接到另外一家公司类似需求的项目,但这家公司想省钱,租用了一个空间,只能用 Access ,不能用 SQL Server ,于是就要求我今天改造原来那个项目的代码。还好不是改成 oracle,不然改动的地方会更多!

version1(头铁硬刚)

这个版本是最基本的数据访问程序,下面以新增用户、获取用户信息为例。

public class Main {
    public static void main(String[] args) {
//        SqlserverUser operator = new SqlserverUser();
        AccessUserOperator operator = new AccessUserOperator();

        // 获取
        operator.GetById(5);

        // 插入
        User user = new User(15, "张三");
        operator.Insert(user);
    }
}

/**
 * 1、User 类,假设只有 ID 和 Name 两个字段,其余省略。
 */
class User {
    private int id;
    private String name;

    public User() {

    }

    public User(int id, String name) {
        this.id = id;
        this.name = 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;
    }
}

/**
 * 2、SqlserverUserOperator 类,用于操作 SQL Server 的 User 。
 */
class SqlserverUserOperator {
    public void Insert(User user) {
        System.out.println("在 SQL Server 中给 User 表增加一条记录");
    }

    public User GetById(int id) {
        System.out.println("在 SQL Server 中根据 ID 得到一条 User 表的记录");
        return null;
    }
}

/**
 * 3、AccessUserOperator 类,用于 Access 的 User 。
 */
class AccessUserOperator {
    public void Insert(User user) {
        System.out.println("在 Access 中给 User 表增加一条记录");
    }

    public User GetById(int id) {
        System.out.println("在 Access 中根据 ID 得到一条 User 表的记录");
        return null;
    }
}

version2 (工厂模式)

之所以 version1 不能换数据库,原因就在于 SqlserverUser operator = new SqlserverUser(); 使得 operator 这个对象被框死在 SQL Server 上了。如果这里是灵活的,专业点的说法,是多态的,那么在执行‘operator.Insert(user);’和‘su.GetUser(1);’时就不用考虑是在用 SQLServer 还是在用 Access 了。

我们可以用工厂方法模式来封装 new SqlserverUser();,这样就不用考虑是在用 SQLServer 还是在用 Access 了。

工厂方法模式是定义一个用于创建对象的接口,让子类决定实例化哪一个类。

public class Main {
    public static void main(String[] args) {
        User user = new User();

        OperatorFactory factory = new SqlServerOperatorFactory();
//        OperatorFactory factory = new AccessOperatorFactory();

        UserOperator operator = factory.CreateUserOperator();

        operator.Insert(user);
        operator.GetUser(5);

    }
}

/**
 * 1、User 类,假设只有 ID 和 Name 两个字段,其余省略。
 */
class User {
    private int id;
    private String name;

    public User() {

    }

    public User(int id, String name) {
        this.id = id;
        this.name = 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;
    }
}

/**
 * 2、UserOperator 类,定义了 User 类相关的操作方法,比如新增用户、查询用户。解除与具体数据库访问的耦合
 */
interface UserOperator {
    void Insert(User user);

    User GetUser(int id);
}

/**
 * 3、SqlserverUserOperator 类,实现了使用 SQL Server 操作 User 类相关的操作方法。
 */
class SqlserverUserOperator implements UserOperator {
    @Override
    public void Insert(User user) {
        System.out.println("在 SQL Server 中给 User 表增加一条记录");
    }

    @Override
    public User GetUser(int id) {
        System.out.println("在 SQL Server 中根据 ID 得到一条 User 表的记录");
        return null;
    }
}

/**
 * 4、AccessUserOperator 类,实现了使用 Access 操作 User 类相关的操作方法。
 */
class AccessUserOperator implements UserOperator {
    @Override
    public void Insert(User user) {
        System.out.println("在 Access 中给 User 表增加一条记录");
    }

    @Override
    public User GetUser(int id) {
        System.out.println("在 Access 中根据 ID 得到一条 User 表的记录");
        return null;
    }
}

/**
 * 5、OperatorFactory 接口,定义了工厂子类具体应该实现哪些类的 Operator
 */
interface OperatorFactory {
    UserOperator CreateUserOperator();
}

/**
 * 6、SqlServerOperatorFactory 工厂类,实现了 OperatorFactory 接口,负责实例化 SqlserverUserOperator 。
 */
class SqlServerOperatorFactory implements OperatorFactory {
    @Override
    public UserOperator CreateUserOperator() {
        return new SqlserverUserOperator();
    }
}

/**
 * 7、AccessOperatorFactory 工厂类,实现了 OperatorFactory 接口,负责实例化 AccessUserOperator 。
 */
class AccessOperatorFactory implements OperatorFactory {
    @Override
    public UserOperator CreateUserOperator() {
        return new AccessUserOperator();
    }
}

image.png

结构:
image.png

但是这样写,代码里还是有指明 new SqlServerOperatorFactory() 呀,要改的地方,依然很多。这个先不急,待会再说,问题没有完全解决,数据库里不可能只有一个User表吧,很可能有其他表,比如增加部门 Department 表。

version3(抽象工厂方法模式)

相对于 version2 ,这次修改了代码,增加了关于部门表的处理。

public class Main {
    public static void main(String[] args) {

        OperatorFactory factory = new SqlServerOperatorFactory();
//        OperatorFactory factory = new AccessOperatorFactory();

        // user 类相关操作
        UserOperator userOperator = factory.CreateUserOperator();

        User user = new User();

        userOperator.Insert(user);
        userOperator.GetUser(5);


        // Department 相关操作
        DepartmentOperator departmentOperator = factory.CreateDepartmentOperator();

        Department department = new Department();

        departmentOperator.Insert(department);
        departmentOperator.GetDepartment(5);
    }
}


/**
 * 1、User 类,假设只有 ID 和 Name 两个字段,其余省略。
 */
class User {
    private int id;
    private String name;

    public User() {

    }

    public User(int id, String name) {
        this.id = id;
        this.name = 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;
    }
}

/**
 * 2、Departmen 类,假设只有 ID 和 departName 两个字段,其余省略。
 */
class Department {
    private int id;
    private String departName;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getDepartName() {
        return departName;
    }

    public void setDepartName(String departName) {
        this.departName = departName;
    }
}

/**
 * 3、UserOperator 类,定义了 User 类相关的操作方法,比如新增用户、查询用户。解除与具体数据库访问的耦合
 */
interface UserOperator {
    void Insert(User user);

    User GetUser(int id);
}

/**
 * 4、DepartmentOperator 类,定义了 Department 类相关的操作方法,比如新增部门、查询部门。解除与具体数据库访问的耦合
 */
interface DepartmentOperator {
    void Insert(Department department);

    Department GetDepartment(int id);
}

/**
 * 5、SqlserverUserOperator 类,实现了使用 SQL Server 操作 User 类相关的操作方法。
 */
class SqlserverUserOperator implements UserOperator {
    @Override
    public void Insert(User user) {
        System.out.println("在 SQL Server 中给 User 表增加一条记录");
    }

    @Override
    public User GetUser(int id) {
        System.out.println("在 SQL Server 中根据 ID 得到一条 User 表的记录");
        return null;
    }
}

/**
 * 6、AccessUserOperator 类,实现了使用 Access 操作 User 类相关的操作方法。
 */
class AccessUserOperator implements UserOperator {
    @Override
    public void Insert(User user) {
        System.out.println("在 Access 中给 User 表增加一条记录");
    }

    @Override
    public User GetUser(int id) {
        System.out.println("在 Access 中根据 ID 得到一条 User 表的记录");
        return null;
    }
}

/**
 * 7、SqlserverDepartmentOperator 类,实现了使用 SQL Server 操作 Department 类相关的操作方法。
 */
class SqlserverDepartmentOperator implements DepartmentOperator {
    @Override
    public void Insert(Department department) {
        System.out.println("在 SQL Server 中给 Department 表增加一条记录");
    }

    @Override
    public Department GetDepartment(int id) {
        System.out.println("在 SQL Server 中根据 ID 得到一条 Department 表的记录");
        return null;
    }
}

/**
 * 8、AccessDepartmentOperator 类,实现了使用 Access 操作 Department 类相关的操作方法。
 */
class AccessDepartmentOperator implements DepartmentOperator {
    @Override
    public void Insert(Department department) {
        System.out.println("在 Access 中给 Department 表增加一条记录");
    }

    @Override
    public Department GetDepartment(int id) {
        System.out.println("在 Access 中根据 ID 得到一条 Department 表的记录");
        return null;
    }
}


/**
 * 9、OperatorFactory 接口,定义了具体工厂应该实现哪些类的 Operator
 */
interface OperatorFactory {
    UserOperator CreateUserOperator();

    DepartmentOperator CreateDepartmentOperator();
}

/**
 * 10、SqlServerOperatorFactory 工厂类,实现了 OperatorFactory 接口,负责实例化 SqlserverUserOperator 。
 */
class SqlServerOperatorFactory implements OperatorFactory {
    @Override
    public UserOperator CreateUserOperator() {
        return new SqlserverUserOperator();
    }

    @Override
    public DepartmentOperator CreateDepartmentOperator() {
        return new SqlserverDepartmentOperator();
    }
}

/**
 * 11、AccessOperatorFactory 工厂类,实现了 OperatorFactory 接口,负责实例化 AccessUserOperator 。
 */
class AccessOperatorFactory implements OperatorFactory {
    @Override
    public UserOperator CreateUserOperator() {
        return new AccessUserOperator();
    }

    @Override
    public DepartmentOperator CreateDepartmentOperator() {
        return new AccessDepartmentOperator();
    }
}

image.png

代码结构图
image.png

这样就可以做到,只需更改

OperatorFactory factory = new SqlServerOperatorFactory();

OperatorFactory factory = new AccessOperatorFactory();

就实现了数据库访问的切换了。

实际上,在不知不觉间,你已经通过需求的不断演化,重构出了一个非常重要的设计模式。

只有一个 User 类和 User 操作类的时候,是只需要工厂方法模式的,但现在显然你数据库中有很多的表,而 SQL Server 与 Access 又是两大不同的分类,所以解决这种涉及到多个产品系列的问题,有一个专门的工厂模式叫抽象工厂模式。

OperatorFactory 是一个抽象工厂接口,里面包含了所有具体工厂应该实现哪些类的 Operator 。

通常是在运行时刻再创建一个具体工厂类的实例,这个实例再创建具有特定实现的产品对象,也就是说,为创建不同的产品对象,客户端应使用不同的具体工厂。

这样做的好处是什么呢?

1、便是易于交换产品系列,由于具体工厂类 ,例如 OperatorFactory factory = new SqlServerOperatorFactory(); 在一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂即可使用不同的产品配置 。我们的设计不能去防止需求的更改,那么我们的理想便是让改动变得最小,现在如果你要更改数据库访问,我们只需要更改具体工厂就可以做到。

2、它让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口操纵实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户代码中 。事实上,你刚才写的例子,客户端所认识的只有 UserOperator 和DepartmentOperator ,至于它是用 SQL Server 来实现还是 Access 来实现就不知道了。

那缺点又有哪些呢?

1、虽然抽象工厂模式可以很方便地切换两个数据库访问的代码,但是如果你的需求来自增加功能,比如我们现在要增加项目表 Project ,你需要增加三个类,ProjectOperator 、SqlserverProjectOperator、AccessProjectOperator,还需要修改两个类, OperatorFactory、SqlserverOperatorFactory 和 AccessOperatorFactory 才可以完全实现。这太麻烦了。

2、还有就是客户端程序类显然不会是只有一个,有很多地方都在使用 UserOperator 或 DepartmentOperator ,而这样的设计,其实在每一个类的开始都需要声明 OperatorFactory factory = new SqlServerOperatorFactory();
如果我有 100 个调用数据库访问的类,是不是就要更改 100 次 OperatorFactory factory = new AccessOperatorFactory(); 这样的代码才行?这不能解决我要更改数据库访问时,改动一处就完全更改的要求。

version4(用简单工厂来改进抽象工厂)

去除 OperatorFactory、SqlServerOperatorFactory 和 AccessOperatorFactory 三个工厂类,取而代之的是 DataAccess 类,用一个简单工厂模式来实现。

public class Main {
    public static void main(String[] args) {
        // user 相关
        UserOperator userOperator = DataAccess.CreateUserOperator();

        User user = new User();
        userOperator.Insert(user);

        userOperator.GetUser(5);


        // departmen 相关
        DepartmentOperator departmentOperator = DataAccess.CreateDepartmentOperator();

        Department department = new Department();

        departmentOperator.Insert(department);
        departmentOperator.GetDepartment(5);
    }
}

/**
 * 1、User 类,假设只有 ID 和 Name 两个字段,其余省略。
 */
class User {
    private int id;
    private String name;

    public User() {

    }

    public User(int id, String name) {
        this.id = id;
        this.name = 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;
    }
}

/**
 * 2、Departmen 类,假设只有 ID 和 departName 两个字段,其余省略。
 */
class Department {
    private int id;
    private String departName;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getDepartName() {
        return departName;
    }

    public void setDepartName(String departName) {
        this.departName = departName;
    }
}

/**
 * 3、UserOperator 类,定义了 User 类相关的操作方法,比如新增用户、查询用户。解除与具体数据库访问的耦合
 */
interface UserOperator {
    void Insert(User user);

    User GetUser(int id);
}

/**
 * 4、DepartmentOperator 类,定义了 Department 类相关的操作方法,比如新增部门、查询部门。解除与具体数据库访问的耦合
 */
interface DepartmentOperator {
    void Insert(Department department);

    Department GetDepartment(int id);
}

/**
 * 5、SqlserverUserOperator 类,实现了使用 SQL Server 操作 User 类相关的操作方法。
 */
class SqlserverUserOperator implements UserOperator {
    @Override
    public void Insert(User user) {
        System.out.println("在 SQL Server 中给 User 表增加一条记录");
    }

    @Override
    public User GetUser(int id) {
        System.out.println("在 SQL Server 中根据 ID 得到一条 User 表的记录");
        return null;
    }
}

/**
 * 6、AccessUserOperator 类,实现了使用 Access 操作 User 类相关的操作方法。
 */
class AccessUserOperator implements UserOperator {
    @Override
    public void Insert(User user) {
        System.out.println("在 Access 中给 User 表增加一条记录");
    }

    @Override
    public User GetUser(int id) {
        System.out.println("在 Access 中根据 ID 得到一条 User 表的记录");
        return null;
    }
}

/**
 * 7、SqlserverDepartmentOperator 类,实现了使用 SQL Server 操作 Department 类相关的操作方法。
 */
class SqlserverDepartmentOperator implements DepartmentOperator {
    @Override
    public void Insert(Department department) {
        System.out.println("在 SQL Server 中给 Department 表增加一条记录");
    }

    @Override
    public Department GetDepartment(int id) {
        System.out.println("在 SQL Server 中根据 ID 得到一条 Department 表的记录");
        return null;
    }
}

/**
 * 8、AccessDepartmentOperator 类,实现了使用 Access 操作 Department 类相关的操作方法。
 */
class AccessDepartmentOperator implements DepartmentOperator {
    @Override
    public void Insert(Department department) {
        System.out.println("在 Access 中给 Department 表增加一条记录");
    }

    @Override
    public Department GetDepartment(int id) {
        System.out.println("在 Access 中根据 ID 得到一条 Department 表的记录");
        return null;
    }
}

/**
 * 9、DataAccess 类,简单工厂模式
 */
class DataAccess {
    private static final String DbType = "Sqlserver";
//    private static final String DbType="Access";

    public static UserOperator CreateUserOperator() {
        UserOperator operator = null;

        switch (DbType) {
            case "Sqlserver":
                operator = new SqlserverUserOperator();
                break;
            case "Access":
                operator = new AccessUserOperator();
                break;
        }

        return operator;
    }

    public static DepartmentOperator CreateDepartmentOperator() {
        DepartmentOperator operator = null;

        switch (DbType) {
            case "Sqlserver":
                operator = new SqlserverDepartmentOperator();
                break;
            case "Access":
                operator = new AccessDepartmentOperator();
                break;
        }

        return operator;
    }
}

image.png

结构
image.png

这个版本的改进确实是比之前的代码要更进一步了,客户端已经不再受改动数据库访问的影响。可以打 95 分。

为什么只能得95分?

原因是如果我需要增加Oracle 数据库访问,本来抽象工厂只增加一个 OracleOperatorFactory 工厂类就可以了,现在就比较麻烦了。需要在 DataAccess 类中每个方法的 swicth 中加 case 。

version5(反射+抽象工厂)

利用 java 的反射机制,可以解决 version4 产生的问题。(为什么只能得95分)

非常遗憾的告诉你,现在这个版本,依然只能得99分。

纳尼(ÒωÓױ)!

public class Main {
    public static void main(String[] args) {
        // user 相关
        UserOperator userOperator = DataAccess.CreateUserOperator();

        User user = new User();
        userOperator.Insert(user);

        userOperator.GetUser(5);


        // departmen 相关
        DepartmentOperator departmentOperator = DataAccess.CreateDepartmentOperator();

        Department department = new Department();

        departmentOperator.Insert(department);
        departmentOperator.GetDepartment(5);
    }
}

/**
 * 1、User 类,假设只有 ID 和 Name 两个字段,其余省略。
 */
class User {
    private int id;
    private String name;

    public User() {

    }

    public User(int id, String name) {
        this.id = id;
        this.name = 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;
    }
}

/**
 * 2、Departmen 类,假设只有 ID 和 departName 两个字段,其余省略。
 */
class Department {
    private int id;
    private String departName;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getDepartName() {
        return departName;
    }

    public void setDepartName(String departName) {
        this.departName = departName;
    }
}

/**
 * 3、UserOperator 类,定义了 User 类相关的操作方法,比如新增用户、查询用户。解除与具体数据库访问的耦合
 */
interface UserOperator {
    void Insert(User user);

    User GetUser(int id);
}

/**
 * 4、DepartmentOperator 类,定义了 Department 类相关的操作方法,比如新增部门、查询部门。解除与具体数据库访问的耦合
 */
interface DepartmentOperator {
    void Insert(Department department);

    Department GetDepartment(int id);
}

/**
 * 5、SqlserverUserOperator 类,实现了使用 SQL Server 操作 User 类相关的操作方法。
 */
class SqlserverUserOperator implements UserOperator {
    @Override
    public void Insert(User user) {
        System.out.println("在 SQL Server 中给 User 表增加一条记录");
    }

    @Override
    public User GetUser(int id) {
        System.out.println("在 SQL Server 中根据 ID 得到一条 User 表的记录");
        return null;
    }
}

/**
 * 6、AccessUserOperator 类,实现了使用 Access 操作 User 类相关的操作方法。
 */
class AccessUserOperator implements UserOperator {
    @Override
    public void Insert(User user) {
        System.out.println("在 Access 中给 User 表增加一条记录");
    }

    @Override
    public User GetUser(int id) {
        System.out.println("在 Access 中根据 ID 得到一条 User 表的记录");
        return null;
    }
}

/**
 * 7、SqlserverDepartmentOperator 类,实现了使用 SQL Server 操作 Department 类相关的操作方法。
 */
class SqlserverDepartmentOperator implements DepartmentOperator {
    @Override
    public void Insert(Department department) {
        System.out.println("在 SQL Server 中给 Department 表增加一条记录");
    }

    @Override
    public Department GetDepartment(int id) {
        System.out.println("在 SQL Server 中根据 ID 得到一条 Department 表的记录");
        return null;
    }
}

/**
 * 8、AccessDepartmentOperator 类,实现了使用 Access 操作 Department 类相关的操作方法。
 */
class AccessDepartmentOperator implements DepartmentOperator {
    @Override
    public void Insert(Department department) {
        System.out.println("在 Access 中给 Department 表增加一条记录");
    }

    @Override
    public Department GetDepartment(int id) {
        System.out.println("在 Access 中根据 ID 得到一条 Department 表的记录");
        return null;
    }
}

/**
 * 9、DataAccess 类,简单工厂模式
 */
class DataAccess {
    private static final String DbType = "Sqlserver";
//    private static final String DbType="Access";

    public static UserOperator CreateUserOperator() {
        UserOperator operator = null;

        // 利用反射原理,避免使用大量 if-else
        String packagePath = SqlserverUserOperator.class.getPackage().getName();
        String className = packagePath + "." + DbType + "UserOperator";

        Class<?> clazz = null;

        try {
            clazz = Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        try {
            // 创建运行时类的对象,需要运行时类有空的构造函数
            operator = (UserOperator) clazz.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        return operator;
    }

    public static DepartmentOperator CreateDepartmentOperator() {
        DepartmentOperator operator = null;

        // 利用反射原理,避免使用大量 if-else
        String packagePath = SqlserverUserOperator.class.getPackage().getName();
        String className = packagePath + "." + DbType + "DepartmentOperator";

        Class<?> clazz = null;

        try {
            clazz = Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        try {
            // 创建运行时类的对象,需要运行时类有空的构造函数
            operator = (DepartmentOperator) clazz.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        return operator;
    }
}

image.png

结构并没有发生变化
image.png

先来分析一下:

现在如果我们增加了 Oracle 数据访问,相关的类的增加是不可避免的,这点无论我们用任何办法都解决不了,不过这叫扩展,开放-封闭原则性告诉我们,对于扩展,我们开放。但对于修改,我们应该要尽量关闭,就目前而言,我们只需要更改

private static final String DbType = "Sqlserver";

private static final String DbType="Oracle";

就可以了。

这样的结果就是 DataAccess.CreateUserOperator() 本来得到的是 SqlserverUserOperator 的实例,而现在变成了 OracleUserOperator 的实例了。

那么如果我们需要增加 Project 产品时,如何做呢?只需要增加三个与 Project 相关的类,再修改 DataAccesss ,在其中增加一个 public static ProjectOperator CreateProjectOperator() 方法就可以了。

version6(反射+配置文件+抽象工厂)

为什么 version5 只能得 99 分呢?

比以前,这代码是漂亮多了。但是,总感觉还是有点缺憾,因为在更换数据库访问时,我还是需要去改程序(改 DbType 这个字符串的值)重编译,如果还是不够优雅,如果能将这些硬编码转换为配置文件读取,就更好了。

具体代码我就省略了。

这下基本可以算是满分了,现在我们应用了反射+抽象工厂模式解决了数据库访问时的可维护、可扩展的问题。

从这个角度上说,所有在用简单工厂的地方,都可以考虑用反射技术来去除 switch 或 if ,解除分支判断带来的耦合。

switch 或者 if 是程序里的好东西,但在应对变化上,却显得老态龙钟。反射技术的确可以很好地解决它们难以应对变化,难以维护和扩展的诟病。