工厂方法?按图索骥!

前言

还记得在第3节的简单工厂模式,我们实现了一个简易计算器。简单工厂模式的最大优点 在于工厂类中包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关类,去除了与具体运算类的依赖。

但其问题也就在这里,如果要加一个'求余'运算符的功能,我们需要在运算工厂类的方法里加'Case'的分支条件的,那这就必然涉及到修改 原有的类?这就等于说,我们不但对扩展 开放了,对修改 也开放了,就违背了开放-封闭原则。那有没有一种方法既可以实现简单工厂方法的作用,又能够避免修改已有的类呢?

有的,那就是我们之后将介绍的工厂方法模式。

什么是工厂方法模式?

工厂方法模式(Factory method),定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。

在之前的简单工厂方法实现中,**工厂类与分支耦合(如果新增一种运算符,需要在工厂类中添加一个分支)。既然工厂类与分支耦合,那么根据依赖倒置原则,我们可以将工厂类抽象成一个接口,**该接口只有一个方法,那就是创建对应的运算符,**通过这种方式,将两者进行解耦合,**其UML类图如下。

工厂方法实现

我们来看看如果使用工厂方法,应该如何实现之前的简易计算器。

再次回顾一下计算器的要求:"实现一个计算器程序,要求输入两个数和运算符号,得到结果,暂时实现加减乘除即可,考虑之后的运算符扩充。"基于工厂方法,我们可以将UML类图设计成这样:

运算符类

运算符类跟第3节相同,在此不再赘述。

cpp 复制代码
package com.whitedew.factorymethod;

//操作类
public class Operation {
    //子类能继承父类的所有属性,但父类若为私有属性,子类只是拥有,无法使用。
    //因此使用protected修饰
    protected double numberA = 0;
    protected double numberB = 0;

    public double getNumberA() {
        return numberA;
    }

    public void setNumberA(double numberA) {
        this.numberA = numberA;
    }

    public double getNumberB() {
        return numberB;
    }

    public void setNumberB(double numberB) {
        this.numberB = numberB;
    }

    public double getResult() {
        double result = 0;
        return result;
    }
}

具体运算类

同第3节的代码,此处省略。

工厂接口

所有的具体运算类都需要一个工厂来实现这个接口。

cpp 复制代码
public interface IFactory {
     Operation createOperation();
}

运算类的工厂

所有的运算类都需要一个对应的工厂,来实现上面的那个工厂接口,并且由这个工厂实例来创建不同的产品实例。以乘法为例:

cpp 复制代码
public class MultiFactory implements IFactory {

    @Override
    public Operation createOperation() {
        return new OperationMulti();
    }
}

计算器的客户端

由于switch case已经从工厂类中移除,于是有关运算符的判断就需要放到客户端来实现了。

cpp 复制代码
package com.whitedew.factorymethod;
public class CalculatorClient {
    public static void main(String[] args) {
        IFactory iFactory = null;
        int numberA = 0;
        int numberB = 0;
        String operationStr = null;

        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入数字A : ");
        // 判断是否还有输入
        if (scanner.hasNext()) {
            numberA = scanner.nextInt();
            System.out.println("输入的数据为:" + numberA);
        }

        System.out.print("请选择运算符(+-*/): ");
        // 判断是否还有输入
        if (scanner.hasNext()) {
            operationStr = scanner.next();
            System.out.println("选择的运算符为:" + operationStr);
        }

        System.out.print("请输入数字B : ");
        // 判断是否还有输入
        if (scanner.hasNext()) {
            numberB = scanner.nextInt();
            System.out.println("输入的数据为:" + numberB);
        }

        switch (operationStr) {
            case "+":
                iFactory = new AddFactory();
                break;
            case "-":
                iFactory = new SubFactory();
                break;
            case "*":
                iFactory = new MultiFactory();
                break;
            case "/":
                iFactory = new DivFactory();
                break;
            default:
                System.out.println("操作符为空");
                System.exit(0);
        }
        Operation operation = iFactory.createOperation();
        operation.setNumberA(numberA);
        operation.setNumberB(numberB);
        double result = operation.getResult();

        System.out.println("结果为" + numberA + operationStr + numberB + "=" + result);

    }
}

结果

运算结果如下,3*5:

总结

从上面的例子可以看出,工厂方法模式是简单工厂模式的进一步抽象和推广。由于使用了多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点(即违背开放-封闭原则)。

但这种方法也带来了另外一个弊端:每加一个产品,就需要加一个产品工厂的类,增加了额外的开发量,但是这个缺点在大部分时候都是可以接收的。同时,由于判断逻辑放在了客户端,增加了客户端的代码开发量,有关这一点,在Java语言中,可以通过抽象工厂模式+反射来实现,详见第5节抽象工厂模式。

参考资料

《大话设计模式》

代码实现

相关推荐
CC数分8 分钟前
统计学基础|先把问题问对:数据分析的六大类型
大数据·人工智能·算法
愚农搬码29 分钟前
企业为什么需要表单引擎,而不是继续手写 Vue 表单?
后端
孟陬37 分钟前
技术周刊#158:ChatGPT 智能 UI 底层原理、个人智能体协议正式发布、一条 POST 请求即可锁死 Next.js 服务
人工智能·后端·全栈
地平线开发者42 分钟前
CLIP 的训练机制:从对比学习到图文对齐
深度学习·算法·自动驾驶
小蒜学长1 小时前
基于SpringBoot+Vue的云南地区扶贫助农系统的设计与实现(代码+数据库+LW)
java·vue.js·spring boot·后端·乡村振兴
码剑客1 小时前
Gin+GORM+Redis 项目开发常见性能坑汇总(Go后端避坑指南)
redis·后端·golang·gin
hetao17338371 小时前
2026-11-02 hetao1733837 的刷题记录
c++·算法
Geektec1 小时前
IMU 选型完全指南:从硬件参数到算法融合的系统工程视角
算法·imu·陀螺仪·加速度计·元器件选型
CQU_JIAKE1 小时前
11.2【A】
算法
java_nnnn1 小时前
Spring Boot 配置文件详解:从基础语法到 YAML 配置
spring boot·后端·github