工厂模式——简单工厂、工厂方法、抽象工厂入门及总结

2023-05-16

背景

相信我们很多开发人员,在平时的系统开发过程中,都或多或少等接触过一些设计模式。比如java程序员必须会接触到的Spring类库,它就综合运用了多种设计模式,从而使源码更加抽象,更加易于拓展和维护。这就是设计模式的优点,它代表了软件开发过程中的代码最佳实践。java发展到现在,公认的有23种设计模式,如果我们这些设计模式都能灵活运用的话,就能极大地提高我们的代码质量和编码效率,能够以更加抽象的思维去阅读源码了。万里之行,始于足下。我们从最简单的设计模式-工厂模式入手,来探索设计模式的奥秘吧!(以下只演示部分关键代码,完整工程请从github:设计模式demo项目上获取)

问题引入1:计算器程序

假设你某一天去面试一家公司,这家公司有这样一道笔试题:请使用面向对象的思想,实现一个简单的计算器程序,要求输入两个数和运算符,得到结果。(不需要严格的校验逻辑,能体现面向对象的思想即可)
看到这个题目,作为一个有java基础的面试者,你心想:这还不简单?于是,你奋笔疾书,一个简单的计算器程序就实现了,代码可能如下:

不采用设计模式

计算器程序1.0

定义计算器类,计算方法calculate接收三个参数:两个操作数和一个运算符。

package com.hejianlin.design_mode_demo.factory.original;
/**
 * 计算机类
 */
public class Operation {
    public double calculate(double num1,double num2,String operate){
        double result=0d;
        switch (operate){
            case "+": result=num1+num2; break;
            case "-": result=num1-num2; break;
            case "*": result=num1*num2; break;
            case "/":
                if(num2==0d){
                    throw new RuntimeException("除数不能为0");
                }
                result=num1/num2;
                break;
            default:
                throw new RuntimeException("运算符非法");
        }
        return result;
    }
}

然后我们写一个测试类,模拟客户端调用,代码如下:

package com.hejianlin.design_mode_demo.factory.original;

public class Test {
    @org.junit.Test
    public void test(){
        Operation operation = new Operation();
        double calculate = operation.calculate(6, 4, "/");
        System.out.println(calculate);
    }
}

运行结果如下:
在这里插入图片描述
嗯,看起来简单的四则运算功能就实现了,但这样子写真的符合题意吗?我们来回顾一下这道面试题:“请使用面向对象的思想”,可以看出,出题人是要考验我们对面向对象的理解。我们知道,面向对象的三个基本特征是:封装、继承、多态。现在来看看我们的计算器1.0程序,他有满足这三个基本特征之一吗?没有!而且,假如这不是一道面试题,而是领导交给你的开发任务,如果你简单粗暴地将代码写成这样,会为以后的代码维护和拓展带来很多隐患:

  1. 耦合度高,难以拓展。我们可以看到,这里计算方法接收操作数为两个,但实际上,不是每种计算都需要两个操作数的,比如开方根就只需要一个操作数。而且,这里的具体计算逻辑,都写在每个case语句下,代码会随着case语句的增加,而变得越发臃肿,无法复用。
  2. 代码维护成本高。每次新增计算类型时,都需要在switch结构中,新增一个case语句,编写具体的计算逻辑。由于之前的其他计算逻辑也写在同个类中,这就导致了每次新增计算类型时,之前的计算类型也要重新参与编译的问题。它不仅违反了设计模式的开闭原则(面对扩展开放,面对修改关闭),而且引入了已有代码被有意或无意篡改导致功能异常的风险。

因此,我们决定对这个程序进行优化,主要思路如下:

  1. 各个计算类型的实现逻辑相互分离,新增计算类型时,不造成之前计算类型的改动。
  2. 由1知每种计算类型的具体实现逻辑,都应该放在各自的类中完成。
  3. 由2知当真正计算时,我们需要判断运算符,来决定实例化哪个计算类。
  4. 综上,我们可以使用简单工厂模式,通过多态的思想来实现。

简单工厂

定义

定义一个产品接口或抽象类,每种具体业务对应着一个产品实现类。定义一个工厂类,由一个工厂对象决定指定产品实例的创建,从而由具体产品实例完成具体的业务功能。(简单工厂模式其实不属于23种设计模式之一,它可以理解为不同工厂模式的一个特殊实现)

计算器程序2.0

首先定义产品抽象类,这里的产品其实就是计算类型(最好是接口,因为设计模式有一条原则就是面向接口编程,这里定义抽象类主要是为了代码最大程度复用,定义了获取参数的方法)

package com.hejianlin.design_mode_demo.factory.simple;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * 计算接口(抽象类)
 */
public abstract class Operation {

    /**
     * 计算方法
     * @author: POI-TECH
     * @date: 2020/2/16 20:58
     * @Param: [num]
     * @return: double
     */
    public abstract double calculate(Double... num);


    /**
     * 取得运算参数
     * @author: POI-TECH
     * @date: 2020/2/16 15:04
     * @Param: [size, num]
     * @return: java.util.List<java.lang.Double>
     */
    protected List<Double> getParam(int size,Double... num){
        List<Double> paramList=new ArrayList<>();

        if(num.length<size){
            throw new RuntimeException("参数个数错误");
        }

        for(int i=0;i<size;i++){
            if(num[i]!=null){
                paramList.add(num[i]);
            }
        }

        if(paramList.size()<size){
            throw new RuntimeException("参数个数错误");
        }

        //转化为不可变列表
        Collections.unmodifiableList(paramList);
        return paramList;
    }
}

接着定义产品的实现类,如下是开方根的实现类:

package com.hejianlin.design_mode_demo.factory.simple.product;

import com.hejianlin.design_mode_demo.factory.simple.Operation;

import java.util.List;

public class Sqrt extends Operation {

    @Override
    public double calculate(Double... num) {
        List<Double> paramList = getParam(1, num);
        return Math.sqrt(paramList.get(0));
    }
}

接着定义工厂类,因为这里是简单工厂模式,所以直接在工厂类中,提供方法,通过判断标识符,得到具体产品实例。

package com.hejianlin.design_mode_demo.factory.simple.factory;

import com.hejianlin.design_mode_demo.factory.simple.Operation;
import com.hejianlin.design_mode_demo.factory.simple.product.*;

public class OperationFactory {
    public static Operation createOperation(String operate){
        Operation operation=null;
        if(operate==null || operate.length()<=0){
            throw new RuntimeException("参数错误");
        }
        switch (operate){
            case "+": operation = new Addition(); break;
            case "-": operation = new Subtraction(); break;
            case "*": operation = new Multiplication(); break;
            case "/": operation = new Division(); break;
            case "sqrt": operation = new Sqrt(); break;
            default:
                throw new RuntimeException("参数错误");
        }
        return operation;
    }
}

最后客户端调用如下:

package com.hejianlin.design_mode_demo.factory.simple;
import com.hejianlin.design_mode_demo.factory.simple.factory.OperationFactory;
public class Test {

    @org.junit.Test
    public void test(){
        Operation sqrt = OperationFactory.createOperation("sqrt");
        double calculate = sqrt.calculate(9.0);
        System.out.println("计算结果:"+calculate);
    }
}

运行结果如下:
在这里插入图片描述

优点与不足

优点

上面改进的计算器程序已经实现了计算参数的合理适配,每种计算类型的具体实现逻辑相互隔离,互不影响。所以可以看出简单工厂模式的优点为:

  1. 能够实现各个产品的业务实现逻辑分离
  2. 产品类中包含了必要的逻辑判断,能够根据客户端的选择条件,动态实例化对应的产品类,去除了对具体产品的依赖
缺点

但我们仔细观察以下,会发现有一些不足之处。当新增了产品类时,使用简单工厂模式的话,势必要修改工厂类的创建实例方法,在方法中添加case分支语句,即修改了原来的类,所以还是违反了开闭原则。所以,我们还是继续优化。这时候,我们就需要使用工厂方法模式了。

工厂方法

定义

定义一个工厂接口,由具体的工厂实现类决定实例化指定的产品。工厂方式使得产品的实例化过程,延迟到工厂子类中。

计算器程序3.0

我们可以看到,它与简单工厂的最大区别是:不直接使用工厂类来实例化产品,而是定义一个工厂接口,由每个具体工厂来实例化指定的产品。所以,我们首先定义一个计算类型的工厂接口:

package com.hejianlin.design_mode_demo.factory.method;

import com.hejianlin.design_mode_demo.factory.simple.Operation;

public interface OperationFactory {
    Operation createProduct();
}

接着定义工厂实现类,在实现类中,实现创建产品实例的抽象方法。以加法工厂实现类为例,利用多态的思想,这里的createProduct方法创建了加法计算类型的实例。

package com.hejianlin.design_mode_demo.factory.method.factory;

import com.hejianlin.design_mode_demo.factory.method.OperationFactory;
import com.hejianlin.design_mode_demo.factory.simple.Operation;
import com.hejianlin.design_mode_demo.factory.simple.product.Addition;

public class AdditionFactory implements OperationFactory {
    @Override
    public Operation createProduct() {
        return new Addition();
    }
}

产品接口和实现类保持不变,修改客户端调用如下:

package com.hejianlin.design_mode_demo.factory.method;

import com.hejianlin.design_mode_demo.factory.method.factory.AdditionFactory;
import com.hejianlin.design_mode_demo.factory.simple.Operation;

public class Test {

    @org.junit.Test
    public void test(){
        OperationFactory factory = new AdditionFactory();
        Operation operation = factory.createProduct();
        double result = operation.calculate(14.0, 18.0);
        System.out.println("计算结果:"+result);
    }
}

计算结果如下:
在这里插入图片描述

问题引入2:数据库访问程序

考虑到计算器程序不太适合抽象工厂模式的演示,所以我们从实际开发场景中出发,用简单的代码,模拟数据库访问,而且,随着问题的深入,我们可以更加深入地了解三种工厂模式的区别以及如何灵活运用。同样的,我们先说说大概的“需求“”:假设业务初始阶段,当前使用的数据库是Sqlserver,有一张用户表,客户端需要连接数据库,对用户表进行操作。简单的demo代码如下:

不采用设计模式

数据库访问程序1.0

定义用户表类,这些类是必须的,也是最核心的,跟数据库类型没有关系,所以这些类通用于数据库访问程序的多个版本。

package com.hejianlin.design_mode_demo.factory.abstract_factory.original_db;

import lombok.Data;

@Data
public class User {
    /**
     * id
     */
    private String id;
    /**
     * 用户名
     */
    private String name;
}

接着定义数据库对象操作类。

package com.hejianlin.design_mode_demo.factory.abstract_factory.original_db;

/**
 * 模拟Sqlserver对user的sql操作语句
 */
public class SqlserverUser {
    /**
     * 插入
     * @param user
     */
    public void insert(User user){
        System.out.println("Sqlserver:插入一条user记录");
    }

    /**
     * 查询
     * @param id
     * @return
     */
    public User select(int id){
        System.out.println("Sqlserver: 查询一条user记录");
        return null;
    }

}

客户端代码如下:

package com.hejianlin.design_mode_demo.factory.abstract_factory.original_db;

public class Test {
    @org.junit.Test
    public void test(){
        User user =new User();
        SqlserverUser sqlserverUser = new SqlserverUser();
        sqlserverUser.insert(user);
        sqlserverUser.select(1);
    }
}

运行结果:
在这里插入图片描述
好了,现在由于业务需要,要将sqlserver数据库切换为Oraclel数据库。有接触过两种数据库的同学都知道,这两种数据库的sql语法差别很大,不能完全复用。在上面的代码中,我们可以看到:数据库表对象的操作类是和数据源强耦合的(SqlserverUser这里表示与Sqlserver数据源强耦合的对象操作类)。如果这时候切换数据库的话,就意味着所有与数据库访问相关的类(除了表对象)和客户端,都需要修改代码。如果这是在平时正常的系统开发中发生的,由于数据库访问遍布系统每个角落,修改数据库就相当于对系统进行了半重构,这样子的工作量和风险可想而知。因此,我们迫切需要具体的对象操作类与数据源解耦,使得操作对象的实例化不受数据源切换的影响。这时候我们就可以使用上面的工厂方法模式来优化了。(也许你会问为什么不用简单工厂?其实会用的,后面再说)

采用工厂方法模式

数据库访问程序2.0

首先定义每个表公用的数据库对象操作(产品)接口,这里为了演示方便,只写了插入和查找

package com.hejianlin.design_mode_demo.factory.abstract_factory.method_db.produce;

import com.hejianlin.design_mode_demo.factory.abstract_factory.original_db.User;

public interface IUserOperation {
    /**
     * 插入
     * @param user
     */
    void insert(User user);

    /**
     * 查找
     * @param id
     * @return
     */
    User select(int id);


}

分别实现Sqlserver和Orcale关于用户表的操作类(产品实现类)。

package com.hejianlin.design_mode_demo.factory.abstract_factory.method_db.produce;

import com.hejianlin.design_mode_demo.factory.abstract_factory.original_db.User;

public class SqlserverUser implements IUserOperation {
    @Override
    public void insert(User user) {
        System.out.println("Sqlserver:插入一条user记录");
    }

    @Override
    public User select(int id) {
        System.out.println("Sqlserver: 查询一条user记录");
        return null;
    }
}

package com.hejianlin.design_mode_demo.factory.abstract_factory.method_db.produce;

import com.hejianlin.design_mode_demo.factory.abstract_factory.original_db.User;

public class OracleUser implements IUserOperation {

    @Override
    public void insert(User user) {
        System.out.println("Oracle:插入一条user记录");
    }

    @Override
    public User select(int id) {
        System.out.println("Oracle: 查询一条user记录");
        return null;
    }
}

然后,按照工厂方法的定义,分别定义工厂接口和两个工厂实现类。

package com.hejianlin.design_mode_demo.factory.abstract_factory.method_db.factory;

import com.hejianlin.design_mode_demo.factory.abstract_factory.method_db.produce.IUserOperation;

public interface IFactory {
    IUserOperation createUser();
}

package com.hejianlin.design_mode_demo.factory.abstract_factory.method_db.factory;

import com.hejianlin.design_mode_demo.factory.abstract_factory.method_db.produce.IUserOperation;
import com.hejianlin.design_mode_demo.factory.abstract_factory.method_db.produce.SqlserverUser;

public class SqlserverFactory implements IFactory {

    @Override
    public IUserOperation createUser() {
        return new SqlserverUser();
    }
}

package com.hejianlin.design_mode_demo.factory.abstract_factory.method_db.factory;

import com.hejianlin.design_mode_demo.factory.abstract_factory.method_db.produce.IUserOperation;
import com.hejianlin.design_mode_demo.factory.abstract_factory.method_db.produce.OracleUser;

public class OracleFactory implements IFactory {

    @Override
    public IUserOperation createUser() {
        return new OracleUser();
    }
}

接着是客户端调用:

package com.hejianlin.design_mode_demo.factory.abstract_factory.method_db;

import com.hejianlin.design_mode_demo.factory.abstract_factory.method_db.factory.IFactory;
import com.hejianlin.design_mode_demo.factory.abstract_factory.method_db.factory.OracleFactory;
import com.hejianlin.design_mode_demo.factory.abstract_factory.method_db.produce.IUserOperation;
import com.hejianlin.design_mode_demo.factory.abstract_factory.original_db.User;

public class Test {

    @org.junit.Test
    public void test(){

        IFactory factory=new OracleFactory();
        IUserOperation userOperation = factory.createUser();

        User user=new User();
        userOperation.insert(user);
        userOperation.select(1);

    }
}

结果如下:
在这里插入图片描述
现在我们来看看问题解决了吗?可以看到,当切换了数据源之后,原有的数据操作类不需要改变(比如从SqlServer切换到Oracle,原先SqlServer相关的代码不用修改),直接新增相关的操作类。客户端调用时,代码几乎也不用修改,只需要在这一句IFactory factory=new OracleFactory(); 指定具体的数据源即可。即通过多态的思想来实现数据源的平滑切换,大大减低了客户端与具体数据库访问的耦合。
然而,这时候就“完美”了吗?未必!现在,我只是演示对用户表进行操作,假如现在要增加部门表呢,那岂不是除了增加Oracle、SqlServer类型的对象操作类(产品),还需要为每个对象操作类(产品)创建一个相应的工厂类?再增加机构表、权限表……呢?这样子,当表越多越来多的话,所需要创建的工厂类也越来越多了,工作量也是相当庞大的。每个产品类对应着数据库相应表的操作,这些是必须要增加的,因此,我们只能设法减少工厂类的数量,所以这里我们使用抽象工厂模式。

抽象工厂

定义

定义一个工厂接口,由具体的工厂实现类决定实例化一系列相关或相互依赖的产品。

数据库访问程序3.0

假如我们现在又增加了一个部门表。我们需要先定义表的对象类,操作接口和实现类。

package com.hejianlin.design_mode_demo.factory.abstract_factory.original_db;

import lombok.Data;

@Data
public class Department {
    /**
     * id
     */
    private String id;
    /**
     * 部门名称
     */
    private String name;
}

package com.hejianlin.design_mode_demo.factory.abstract_factory.abstract_db.produce;

import com.hejianlin.design_mode_demo.factory.abstract_factory.original_db.Department;
import com.hejianlin.design_mode_demo.factory.abstract_factory.original_db.User;

public interface IDepartmentOperation {

    /**
     * 插入
     * @param department
     */
    void insert(Department department);

    /**
     * 查找
     * @param id
     * @return
     */
    Department select(int id);


}

package com.hejianlin.design_mode_demo.factory.abstract_factory.abstract_db.produce;

import com.hejianlin.design_mode_demo.factory.abstract_factory.original_db.Department;

public class OracleDepartment implements IDepartmentOperation{

    @Override
    public void insert(Department department) {
        System.out.println("Oracle:插入一条department记录");
    }

    @Override
    public Department select(int id) {
        System.out.println("Oracle:查询一条department记录");
        return null;
    }
}

package com.hejianlin.design_mode_demo.factory.abstract_factory.abstract_db.produce;

import com.hejianlin.design_mode_demo.factory.abstract_factory.original_db.Department;

public class SqlserverDepartment implements IDepartmentOperation{

    @Override
    public void insert(Department department) {
        System.out.println("Sqlserver:插入一条department记录");
    }

    @Override
    public Department select(int id) {
        System.out.println("Sqlserver:查询一条department记录");
        return null;
    }
}

最核心的代码来了:我们这里需要修改原来的工厂接口,增加一个创建部门实例的方法,由每个实现类实现。

package com.hejianlin.design_mode_demo.factory.abstract_factory.abstract_db.factory;

import com.hejianlin.design_mode_demo.factory.abstract_factory.abstract_db.produce.IDepartmentOperation;
import com.hejianlin.design_mode_demo.factory.abstract_factory.abstract_db.produce.IUserOperation;

public interface IFactory {
    IUserOperation createUser();
    IDepartmentOperation createDepartment();
}

package com.hejianlin.design_mode_demo.factory.abstract_factory.abstract_db.factory;

import com.hejianlin.design_mode_demo.factory.abstract_factory.abstract_db.produce.IDepartmentOperation;
import com.hejianlin.design_mode_demo.factory.abstract_factory.abstract_db.produce.IUserOperation;
import com.hejianlin.design_mode_demo.factory.abstract_factory.abstract_db.produce.OracleDepartment;
import com.hejianlin.design_mode_demo.factory.abstract_factory.abstract_db.produce.OracleUser;

public class OracleFactory implements IFactory {

    @Override
    public IUserOperation createUser() {
        return new OracleUser();
    }

    @Override
    public IDepartmentOperation createDepartment() {
        return new OracleDepartment();
    }
}

package com.hejianlin.design_mode_demo.factory.abstract_factory.abstract_db.factory;

import com.hejianlin.design_mode_demo.factory.abstract_factory.abstract_db.produce.IDepartmentOperation;
import com.hejianlin.design_mode_demo.factory.abstract_factory.abstract_db.produce.IUserOperation;
import com.hejianlin.design_mode_demo.factory.abstract_factory.abstract_db.produce.SqlserverDepartment;
import com.hejianlin.design_mode_demo.factory.abstract_factory.abstract_db.produce.SqlserverUser;

public class SqlserverFactory implements IFactory {

    @Override
    public IUserOperation createUser() {
        return new SqlserverUser();
    }

    @Override
    public IDepartmentOperation createDepartment() {
        return new SqlserverDepartment();
    }
}

也许到这里你会疑惑?这个模式怎么跟工厂方法那么像呢?其实我们可以将工厂方法模式看出一种特殊的抽象工厂模式。所不同的是:工厂方法的每个工厂实现类,只能实例化一种指定的产品,而抽象工厂则一个工厂实现类可以实例化多个相关或有相互依赖关系的产品(注意是有相关或有相互依赖关系的产品)。
客户端代码如下:

package com.hejianlin.design_mode_demo.factory.abstract_factory.abstract_db;

import com.hejianlin.design_mode_demo.factory.abstract_factory.abstract_db.factory.IFactory;
import com.hejianlin.design_mode_demo.factory.abstract_factory.abstract_db.factory.OracleFactory;
import com.hejianlin.design_mode_demo.factory.abstract_factory.abstract_db.produce.IDepartmentOperation;
import com.hejianlin.design_mode_demo.factory.abstract_factory.abstract_db.produce.IUserOperation;
import com.hejianlin.design_mode_demo.factory.abstract_factory.original_db.Department;
import com.hejianlin.design_mode_demo.factory.abstract_factory.original_db.User;

public class Test {

    @org.junit.Test
    public void test(){
        IFactory factory=new OracleFactory();
        IUserOperation userOperation = factory.createUser();
        IDepartmentOperation departmentOperation = factory.createDepartment();

        System.out.println("用户操作:");
        User user = new User();
        userOperation.insert(user);
        userOperation.select(1);


        System.out.println("部门操作:");
        Department department = new Department();
        departmentOperation.insert(department);
        departmentOperation.select(1);
    }
}

运行结果:
在这里插入图片描述
我们可以看到,这时候再新增表时,除了必要的表对象和操作类对象的代码需要编写时,工厂实现类数量还是保持不变,只需要修改工厂接口,增加一个新的创建操作类对象实例的方法,并交由工厂实现类实现即可。
然而,我们来重新看看上面的客户端代码,这里的IFactory factory=new OracleFactory();还是明确指定了数据库类型。作为客户端,它应该只需要满足正常的数据库操作即可,而不必,最好也不要关心数据库的类型。而且一旦修改了数据库类型,客户端也还要重新修改代码,指向新的数据库类型。在实际的开发中,客户端访问数据库的代码分散在四处,代码也不好维护。于是,我们应该提供一个通用的类,在类内部预先指定了数据库类型,客户端使用这个类的方法进行数据库访问,而无需知道具体的数据库类型!

使用简单工厂和反射优化抽象工厂

数据库访问程序4.0

由于现在有多个数据库工厂,为了让客户端感知不到数据库的切换。我们可以为这些工厂再新建一个工厂。即将这些工厂当做产品,由唯一一个工厂来实例化。同时为了去除 switch-case等“丑陋”的代码,以及防止硬编码,支持可配置,我们又使用了java反射来做了进一步优化。代码如下:
首先,在Springboot项目的配置文件,默认名称是application.properties下,新增配置项db_type.packagedb_type.factory,前者表示具体工厂类的父包路径,后者表示具体的数据库工厂类名。

db_type.package=com.hejianlin.design_mode_demo.factory.abstract_factory.abstract_db.factory
db_type.factory=OracleFactory

接着我们定义DataAccess类,它能直接被客户端调用,实例化数据库工厂,而无需知道具体的数据库类型。

package com.hejianlin.design_mode_demo.factory.abstract_factory.access_config_db;

import com.hejianlin.design_mode_demo.factory.abstract_factory.abstract_db.factory.IFactory;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * 取得配置文件中的配置,并通过反射的方式,实例化数据库工厂
 * 由于这里使用了Spring的注解获取配置,所以这里需要使用@Component将DataAccess声明为Spring容器的一个bean
 */
@Component
@Data
public class DataAccess {

    @Value("${db_type.package}")
    private String parentDir;

    @Value("${db_type.factory}")
    private String factoryClass;


    public IFactory createFactory(){
        IFactory factory= null;

        try {
            Class<?> cls = Class.forName(parentDir+"."+factoryClass);
            factory=(IFactory)cls.newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }

        if(factory==null){
            throw new RuntimeException("初始化数据库配置失败!");
        }
        return factory;
    }

}

我们可以看到,这个类提供了createFactory方法,返回了数据库工厂实例,内部获取配置文件中的具体工厂类的路径,通过反射,实例化数据库工厂。当切换数据库时,除了必要的新增代码外,只需修改配置文件中关于具体工厂路径的配置即可,客户端完全不用改动!客户端代码如下:

package com.hejianlin.design_mode_demo.factory.abstract_factory.access_config_db;

import com.hejianlin.design_mode_demo.DesignModeDemoApplication;
import com.hejianlin.design_mode_demo.factory.abstract_factory.abstract_db.factory.IFactory;
import com.hejianlin.design_mode_demo.factory.abstract_factory.abstract_db.produce.IDepartmentOperation;
import com.hejianlin.design_mode_demo.factory.abstract_factory.abstract_db.produce.IUserOperation;
import com.hejianlin.design_mode_demo.factory.abstract_factory.original_db.Department;
import com.hejianlin.design_mode_demo.factory.abstract_factory.original_db.User;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = DesignModeDemoApplication.class)
public class Test {

    @Autowired
    private DataAccess dataAccess;

    @org.junit.Test
    public void test(){

        IFactory factory = dataAccess.createFactory();

        User user = new User();
        IUserOperation userOperation = factory.createUser();
        userOperation.insert(user);
        userOperation.select(1);

        Department department = new Department();
        IDepartmentOperation departmentOperation = factory.createDepartment();
        departmentOperation.insert(department);
        departmentOperation.select(1);
    }
}

运行结果:
在这里插入图片描述

总结与思考

综上,工厂模式相关的三种模式都已经介绍完了,现在我们从三个方面做一下总结吧。

实现方式特点适用场景
简单工厂直接在一个类中,通过判断(比如通过case语句匹配标识字符串)的方式实例化对应的产品只需唯一的工厂类,可添加业务逻辑,判断实例哪一个产品,但是需要维护判断语句,而且违背了开闭原则与客户端完全解耦的场景(当简单工厂利用反射机制优化后,客户端就不用传特定的标识,这时候相对于客户端来说完全解耦了)
工厂方法通过接口实现的方式,为每个产品指定一个工厂遵循了开闭原则(不考虑客户端),一个工厂对应着一个产品新旧功能模块隔离,互不影响的场景
抽象工厂在工厂方法的基础上,为多个相关或有相互依赖关系的产品指定同一个工厂遵循了开闭原则(不考虑客户端),一个工厂对应着一系列有关联关系的产品有相同性质或有关联关系的多个对象的管理的场景
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

工厂模式——简单工厂、工厂方法、抽象工厂入门及总结 的相关文章

  • 【第八章:设计模式】第29节:面试常考设计模式(下)

    大家好 本小节中我们继续交流学习常见的设计模式 在上一小节中 我们介绍了设计模式的六大原则 并且重点阐述了单例模式的多种写法 单例模式也是为数不多的可以在面试中直接 手撕 的设计模式 除了单例模式外 在面试中考察的其余常见设计模式一般都不会
  • 【第九章:面试分享】第30节:应聘者角度分享面经与面试秘籍

    大家好 非常感谢大家可以和我一起进行交流学习 在前面8个章节中 我竭尽所能的对Java开发岗的常见重要知识点进行了梳理总结 力争用最通俗易懂的语言表达出来 使得大家可以轻松掌握其技术原理 在第九章中 我将进行一些面试分享 本小节中 我将和大
  • 【第九章:面试分享】第31节:面试官角度分享面试与学习方法

    大家好 上一小节我们分享了一些真实的实习和校招面经 并且讲述了作为应聘者如何高效准备和应对面试 本小节 我转变身份 站在面试官的角度来分享总结面试 本小节的主要内容如下 面试中 大多数应聘者是如何表现的 作为面试官的我如何考察应聘者 一些重
  • 【加餐篇:社招分享】第32节:工作两年,大厂社招Offer拿到手软

    大家好 在专刊初版完结多日之后 我们又见面了 本专刊的主题是Java开发岗位高频面试题解析 力争从多个知识模块上进行总结 以一种全而精的方式来给大家介绍相关知识点以及其实现原理 回答话术等 相信本专刊对于大家的学习与面试都有一个很大的帮助
  • Git:一文阐述常见命令的使用

    前言 为什么写这篇Git文章 xff1f 在日常的需求开发中 xff0c 发现部分同学不太熟悉Git命令 xff0c 往往是通过idea自带的一些工具来执行简单的Git命令 xff0c 遇到一些突发问题的时候 xff0c 往往不知所措 简单
  • 一种通用的静态资源发布方法

    技术问题 xff1a 在日常的用户页面中 xff0c 往往会存在一些常见问题页面 常见问题页面一般情况下分类 分条目的阐述当前用户侧容易遇到的一些问题 一般情况下 xff0c 问题会比较固定 xff0c 其对应的回答或者解释也比较固定 xf
  • 【回归贴】同志们,阔别三年,我回来啦~

    背景 熟悉的感觉 xff0c 接近三年没有好好写博客了 xff0c 因为各种各样的事情吧 在20年初跳槽到当前公司后 xff0c 因为工作繁忙等各种原因 xff0c 我几乎要放弃了写作 但是 xff0c 人不可能一直呆在舒适圈 xff0c
  • Java同步集合synchronizedX中的迭代器Iterator使用,为什么需要使用者加锁?

    xff08 尊重劳动成果 xff0c 转载请注明出处 xff1a https yangwenqiang blog csdn net article details 129472842冷血之心的博客 xff09 前言 大家好 xff0c 我是
  • 从帝王之术中窥探天机

    前言 闲来无事 xff0c 过年期间追上了一部古老的大型古装历史电视连续剧 朱元璋 xff0c 由冯小宁执导 xff0c 胡军 剧雪 郑晓宁 鄂布斯等主演的历史题材电视剧 曾经看过陈道明主演的 康熙王朝 xff0c 也看过唐国强主演的 雍正
  • Gin框架 ShouldBindJSON详解

    为什么第二次使用ShouldBindJSON就失效了呢 今天debug看了下 xff0c 主要是 http Request的io buffer第一次取完之后 xff0c http body 结构体中的sawEOF 61 true 第二次去读
  • linux c语言 线程资源释放

    最近做了多线程并发网络编程的时候出现了一个问题 程序在运行的过程中 占用的内存会越来越大 起初我怀疑是程序有指针没有被 free 导致内存泄漏 在查代码的过程中我发现 xff0c 我并没有手动收回创建的线程资源 通过上网查阅linux线程资
  • 三十五、PHP7 MongDB 扩展安装与使用

    在前面的章节中我们学习了 MongoDB PHP 扩展使用范例 xff0c 不过那篇文章只能针对 PHP5 使用 xff0c PHP7 以上版本则需要使用其它的 PHP MongoDB 扩展 PHP7 Mongdb 扩展安装 假设我们的 P
  • Linux 创建和回收swap分区

    1 查看当前内存使用情况 xff1a free m 2 增加swap分区 xff0c 8G dd if 61 dev zero of 61 swap bs 61 1024 count 61 8192000 swap文件可以放在空间比较大的盘
  • VS2015 + WDK10

    前几天刚刚用VS2008配置好了WDK7600 xff0c 各种配置 xff0c 又是修改项目包含文件目录和库目录 xff0c 还要修改项目属性 xff0c 折腾了半天debug下终于生成了 sys文件 xff08 驱动文件 xff09 x
  • Flutter VS React Native,应该选哪个?

    移动行业渴望进行一场革命 xff0c 以遏制移动应用程序开发过程中出现的成本高 耗时长等问题 因此 xff0c 该变革以跨平台开发的形式出现 现在 xff0c 维护代码和开发应用程序对于开发人员来说变得简单且省时 那么对于开发者来说 xff
  • VScode如何去打开html页面

    首先在打开VsCode 找到扩展商店 搜索open in browser插件 第二步在html页面中点击右键 找到Open In Default Browser 打开浏览器 Open In Other Browser 选择其他的浏览器打开
  • 2、SPSS的基本知识

    目录 一 SPSS软件的安装和启动 二 SPSS的基本操作环境 xff08 1 xff09 数据编辑窗口 xff08 主程序窗口 xff09 xff08 2 xff09 SPSS结果输出窗口 三 SPSS软件的退出 四 SPSS软件的三种基
  • STM32F429IGT6移植FreeRTOS时遇到的问题汇总

    一 学习环境 编译环境 xff1a keil 开发板 xff1a 野火STM32F429开发板 二 遇到的问题 问题一 xff1a error This port can only be used when the project opti
  • 一篇搞懂关于计算机的减法运算

    一篇搞懂关于计算机的减法运算 减法相减结果为正的减法相减结果为负数的减法 减法 相减结果为正的减法 如下一篇拙言 xff0c 是自己平时的总结 xff0c 如有错误欢迎各位大佬指正 相信你一定听说过 xff0c 补码 xff0c 取反加一等
  • 使用Dev C++建立工程文件调用不同文件下的c文件

    在学校嵌入式软件小组课上直播翻车 xff0c 很尴尬 xff01 xff01 xff01 xff01 然后我结束以后仔细找了一歘啊错误原来是因为没有主一头文件的包含形式导致的 我先介绍一下C语言包含头文件时 lt gt 和 34 34 区别

随机推荐

  • Java Spring Boot 热部署

    一 IDEA配置 当我们修改了Java类后 xff0c IDEA默认是不自动编译的 xff0c 而spring boot devtools又是监测classpath下的文件发生变化才会重启应用 xff0c 所以需要设置IDEA的自动编译 x
  • 新手上路——树莓派3B+系统安装

    首先拿到树莓派后是万分开心 xff0c 索然我不知道他是个什么 xff0c 能用来干什么 xff0c 不过貌似是个电脑主机 xff0c 所以就上手玩儿了 刚拿到手里然后去树莓派基金会网站 xff08 https www raspberryp
  • 新手上路——树莓派3B+安装cmake

    安装smake有两种方法地一种是自动安装 第二种是手动安装 建议使用手动安装 xff0c 自动安装不一定是你想要的版本 xff0c 现介绍第一种 方案一 xff1a sudo apt install cmake 这样就结束了方便 快捷 方案
  • 新手上路——树莓派3B+安装OpenCV(你想要的版本)

    啊啊啊啊啊 xff01 xff01 安装六七次都没成功的我近乎崩溃 xff0c 这次终于成功了 xff0c 失败了无数次的我好像让人来教我一下啊 xff0c 可惜没有 xff0c 还是自己琢磨薄吧 xff0c 为了帮助到更多的人我写了一下我
  • 舵机控制使用

    本文仅介绍固定180度舵机 MG996R舵机 SG90舵机 MG90S舵机等 引脚功能 xff1a 舵机的转动角度跟输入脉冲有关 xff0c 详细看下表 xff1a 可以发现一个规律 xff1a 角度每次增加45度 xff0c 高电平时间相
  • Python之You-Get库学习

    今天学习一下You Get这个第三方库 xff0c 体验一下视频下载的快乐 简介 You Get库是一个基于Python3的视频下载工具 xff0c 支持多数国内外主流视频站点的视频下载 看一下项目主页的README md 真的是十分的ni
  • CMake编译CUDA项目报错

    CMake编译CUDA项目报错 现象解决方法结果 现象 configure后显示如下错误 CMake Error at C Program Files CMake share cmake 3 26 Modules CMakeDetermin
  • 程序设计思维 week11 作业

    A 题意 蒜头君从现在开始工作 xff0c 年薪 NN 万 他希望在蒜厂附近买一套 6060 平米的房子 xff0c 现在价格是 200200 万 假设房子价格以每年百分之 KK 增长 xff0c 并且蒜头君未来年薪不变 xff0c 且不吃
  • windows->wsl&ubuntu config

    ref https blog csdn net weixin 45883933 article details 106085184 安装前配置 ref 适用于 Linux 的 Windows 子系统安装指南 Windows 10 1 启用
  • 线程阻塞与唤醒

    线程阻塞与唤醒的方法如图 xff1a span class hljs keyword package span newThread span class hljs keyword import span java util Scanner
  • Android的死机、重启问题分析方法

    Android的死机 重启问题分析方法 原文链接 xff1a https blog csdn net jinlu7611 article details 50592385 1 死机现象 1 1 死机定义 当手机长时间无法再被用户控制操作时
  • JAVA小白工具人PHPer

    PHP写多了 xff0c 确实很松散 xff0c 什么都是array xff0c 不像java这么资深 xff0c 个个都是大佬 慢慢学习 1 二维数组取某一列的值 php的写法 array column array 39 id 39 ja
  • 当大恒相机采集帧率没有达到理论帧率时

    当大恒相机采集帧率没有达到理论帧率时 xff0c 可设置最大带宽
  • iOS——UITabBarController

    一 UITabBarController简介 1 UITabBarController 对象可以保存多个 视图控制器 并进行切换 xff0c 并且底部还会有一个 工具栏 xff08 UITabBar xff09 xff0c 工具栏里会有多个
  • nomachine NX 远程连接相关问题

    由于xavier等硬件设备不支持x86指令集 xff0c 因此无法使用诸如teamviewer 向日葵 todesk等远程控制软件 这里我们使用nomachine软件实现远程访问 远程连接分以下两种情况 xff1a 局域网内远程连接和公网远
  • loki部署

    官方最佳示例 xff1a Best practices Grafana Loki documentation helm repo add loki https grafana github io loki charts helm repo
  • C++中的string与char数据类型以及路径字符串拼接以及写txt文件的问题

    作为一个计算机小白 xff0c 最近学习C 43 43 xff0c 这里做一个小记录 xff0c 欢迎各路大神共同交流 分割线 C 43 43 中string与char的区别 xff1a 在C 43 43 中 xff0c 字符串有两种 xf
  • 计蒜客救援问题 java

    救生船从大本营出发 xff0c 营救若干屋顶上的人回到大本营 xff0c 屋顶数目以及每个屋顶的坐标和人数都将由输入决定 xff0c 求出所有人都到达大本营并登陆所用的时间 在直角坐标系的原点是大本营 xff0c 救生船每次从大本营出发 x
  • 在keil中观察STM32产生的PWM 用示波器观察实质频率与仿真不符 是计算值的10倍(疑惑中。。。)

    第一步 xff1a 选择软件仿真 1 配置软件仿真 第二步 xff1a 进入仿真 xff0c 调用logicc analyer 观察波形 1 按仿真按键进入仿真界面 2 打开模拟 示波器 界面 3 设置观察端口 4 开始仿真 全速运行 5
  • 工厂模式——简单工厂、工厂方法、抽象工厂入门及总结

    背景 相信我们很多开发人员 xff0c 在平时的系统开发过程中 xff0c 都或多或少等接触过一些设计模式 比如java程序员必须会接触到的Spring类库 xff0c 它就综合运用了多种设计模式 xff0c 从而使源码更加抽象 xff0c