品设计模式 - (创建型) 工厂模式 Factory Method

参考

  1. 工厂模式 - pdai
  2. 工厂方法详解
  3. 工厂模式快速入门
  4. 《图解设计模式》 ------ Factory Method

快速入门


主要作用

将类的实例化(具体产品的创建)延迟到工厂类的子类(具体工厂)中完成,即由子类来决定应该实例化(创建)哪一个类。

工厂方法模式的本质

延迟到 子类选择实现

简单工厂存在的问题

  1. 工厂类集中产品创建逻辑,若工厂无法正常工作,系统受影响。
  2. 违背"开放 - 关闭原则",添加新产品需改工厂类逻辑致其复杂。简单工厂模式用静态方法,不能被继承重写,工厂角色难形成基于继承的等级结构。

简单示例

工厂抽象类 Factory

java 复制代码
abstract class Factory {

    abstract Product factoryMethod();

    abstract void showProductName();
}

具体工厂类

java 复制代码
public class ConcreteFactoryA extends Factory{

    @Override
    Product factoryMethod() {
        return new ConcreteProductA();
    }

    @Override
    void showProductName() {
        System.out.println(this.factoryMethod().getProductName());
        System.out.println(this.getClass().getSimpleName() + " do something ....");
    }
}
java 复制代码
public class ConcreteFactoryB extends Factory {
    @Override
    Product factoryMethod() {
        return new ConcreteProductB();
    }

    @Override
    void showProductName() {
        System.out.println(this.factoryMethod().getProductName());
        System.out.println(this.getClass().getSimpleName() + " do something ....");
    }
}

抽象商品接口

java 复制代码
public interface Product {

    String getProductName();
}

具体商品接口

java 复制代码
public class ConcreteProductA implements Product{
    @Override
    public String getProductName() {
        return "Product A";
    }
}

public class ConcreteProductB implements Product{
    @Override
    public String getProductName() {
        return "Product B";
    }
}

客户类

java 复制代码
public class Client {

    public static void main(String[] args) {

        Factory factory = new ConcreteFactoryA();
        String factoryProductName = factory.factoryMethod().getProductName();
        System.out.println(factoryProductName);
        factory.showProductName();

        factory = new ConcreteFactoryB();
        factoryProductName = factory.factoryMethod().getProductName();
        System.out.println(factoryProductName);
        factory.showProductName();

        /*
        输出:
        Product A
        ConcreteFactoryA do something ....
        Product B
        ConcreteFactoryB do something ....
        */
    }
}

优缺点

优点

  1. 更加符合开闭原则: 新增一种产品时,只需要增加相应的具体产品类和相应的工厂子类即可不需要像简单工厂模式那样修改工厂类的判断逻辑

  2. 符合单一职责原则: 每个具体工厂类只负责创建对应的产品,简单工厂中的工厂类存在复杂的 switch逻辑判断

  3. 不使用静态工厂方法,可以形成基于继承的等级结构;而简单工厂模式使用了静态工厂方法


缺点

  1. 添加新产品需增加对应工厂类,系统类成对增加,增加 复杂度与开销
  2. 考虑可扩展性引入抽象层,增加抽象性和理解难度,实现可能用 DOM、反射等技术,增加实现难度。
  3. 虽保证工厂方法内对修改关闭,但更换产品仍需修改具体工厂类。一个具体工厂只能创建一种具体产品。

总结:工厂模式是简单工厂模式的抽象拓展,保留封装优点,使扩展简单、继承可行且增加多态性体现。

入门案例

设计一个 Card 开户系统,实现制作身份证 (IDCard)的作用

Product 类和 Factory 类属于 framework 包。组成了生成实例的框架

IDCard 类和 IDCardFactory 类负责实际的加工处理,它们都属于 idcard 包

Client 类是用于测试程序行为的类

类图设计 UML

核心组件

  1. Product (产品)

Product 角色属框架方抽象类,定义工厂方法模式生成示例的 API,具体处理由子类ConcreteProduct 角色确定。。

  1. Creator (创建者)

框架一方生成 Product 角色抽象类,具体处理由 ConcreteCreator 决定。调用 Product 角色及生成实例方法可生成 Product 实例,非用 new 关键字,而是调用专用方法,防止父类与其他具体类耦合。。

  1. ConcreteCreator (具体的创建者)

具体加工的一方,决定了具体的产品。

  1. ConcreteProduct ( 具体的创建者 )

ConcreteCreator 角色属于具体加工的一方。

代码实现

  • 新建 framework 包:放置框架相关抽象类
  • 新建 idcard 包:存放具体的加工实现类

Product

仅声明了 use 抽象方法。use 方法的实现则被交给了 Product 类的子类负责

java 复制代码
package com.jools.designpattern.create.factory.framework;


public abstract class Product {

    /**
     * 抽象方法 use; 定义了产品是 "任意可以 user 的" 东西
     */
    public abstract void user();
}

Factory 类 (抽象)

借助模板设计模式

声明用于 生成产品createProduct抽象方法和用于 注册产品registerProduct 抽象方法。

  • 生成产品
  • 注册产品

具体实现交给 Factory 子类负责。

生成具体 Product类的逻辑:

  1. 先调用 createProduct 生成产品
  2. 接着调用 registerProduct 注册产品

具体的实现内容根据工厂方法模式适用的场景不同而不同

java 复制代码
package com.jools.designpattern.create.factory.framework;


public abstract class Factory {

    public final Product create(String owner) {
        Product product = createProduct(owner);
        registerProduct(product);
        return product;
    }

    /**
     * 生成产品
     *
     * @param owner 账户姓名
     * @return
     */
    protected abstract Product createProduct(String owner);

    /**
     * 注册产品
     *
     * @param product
     * @return
     */
    protected abstract void registerProduct(Product product);
}

IDCard 类

负责加工处理的一类;实现了抽象的 Product 接口

java 复制代码
package com.jools.designpattern.create.factory.idcard;

public class IDCard extends Product {

    private String owner;

    IDCard(String owner) {
        System.out.println("制作" + owner + " 的 ID 卡");
        this.owner = owner;
    }

    @Override
    public void use() {
        System.out.println("使用了" + owner + " 的 ID 卡");
    }

    public String getOwner() {
        return owner;
    }
}

注意:IDCard 的构造函数并不是 public; 表示仅可以在同包下访问。体现了 '框架' 包和 '具体实现' 包下的解耦

IDCardFactory 类

实现了 createProductregisterProduct 方法

  • createProduct方法通过生成 IDCard 的实例来生产产品
  • registerProduct方法则通过将 IDCard的 owner 持有人保存到 owners字段来实现注册产品
java 复制代码
package com.jools.designpattern.create.factory.idcard;


public class IDCardFactory extends Factory {

    private List<String> owners = new ArrayList<>();

    @Override
    protected Product createProduct(String owner) {
        return new IDCard(owner);
    }

    @Override
    protected void registerProduct(Product product) {
        owners.add(((IDCard) product).getOwner());
    }

    //返回账户
    public List<String> getOwners() {
        return owners;
    }
}

测试 - 客户端类

java 复制代码
package com.jools.designpattern.create.factory;


public class IDCardClient {

    public static void main(String[] args) {

        Factory factory = new IDCardFactory();
        Product card1 = factory.create("AAA");
        Product card2 = factory.create("BBB");
        Product card3 = factory.create("CCC");

        card1.use();
        card2.use();
        card3.use();

        List<String> owners = ((IDCardFactory) factory).getOwners();
        for (String name : owners) {
            System.out.println(name);
        }
        /*
         输出:
            制作AAA 的 ID 卡
            制作BBB 的 ID 卡
            制作CCC 的 ID 卡
            使用了AAA 的 ID 卡
            使用了BBB 的 ID 卡
            使用了CCC 的 ID 卡
            AAA
            BBB
            CCC
         */
    }
}

扩展思路

  • "框架" 和 "具体加工" 两方面内容分别被封装到 frameworkidcard包中
  • 举例使用 产品工厂

比如要创建表示电视机类的 Television和表示电视机厂的 TelevisonFactory。 这时我们仅需要引入 framework包就可以进一步编写具体实现类的 television

(补充)生成具体实现的三种方式


Factory 类的 createProduct方法是抽象方法,也就是说需要在子类中实现该方法。

createProcut 方法的实现方式一般有以下 3 种

指定其为抽象方法

劣势: 一旦定义为抽象方法,子类就必须实现该方法。如果子类不实现该方法,编译器就会报告编译错误。

java 复制代码
abstract class Factory {
    public abstract Product createProduct(String name) {
        
    }
}

为其实现默认处理

如果子类没有实现该方法,将进行默认的实现

劣势: 但是,这时使用 new 关键字,因此不能将 Product 类定义为抽象类; 仅能切换为接口

java 复制代码
class Factory {
    public Product createProduct(String name) {
        return new Product(name);
    }
}

在其中抛出异常

createProduct 方法的默认处理为抛出异常,这样一来,如果未在子类中实现该方法,程序就会在运行时报错

java 复制代码
class Factory {
    public Product createProduct(String name) {
        throw new FactoryMethodRuntimeException();	//可自定义实现异常类
    }
}

思考问题


一、为什么 IDCard 类的构造函数不是 public?

java 复制代码
package com.jools.designpattern.create.factory.idcard;

public class IDCard extends Product {

    private String owner;

    IDCard(String owner) {
        ....
    }

}

个人见解:

保证具体实现类仅能具体实现类包 即: idcard 访问

实现 framework 和 具体实现层 隔离

防止抽象框架层和具体实现耦合

二、修改为 IDCard 添加卡的编号,并且在 IDCardFactory 类中保存编号与所有者之间的对应表

仅需要修改具体实现类

修改 IDCard

java 复制代码
package com.jools.designpattern.create.factory.idcard;

public class IDCard extends Product {

    private String owner;

    //新增编号
    private String no;

    IDCard(String owner, String no) {
        System.out.println("制作" + owner + " 的 ID 卡 ------ " + " 编号:" + no);
        this.owner = owner;
        this.no = no;
    }


    @Override
    public void use() {
        System.out.println("使用了" + owner + " 的 ID 卡");
    }

    public String getOwner() {
        return owner;
    }

    //新增getter
    public String getNo() {
        return no;
    }
}

修改 IDCardFactory

java 复制代码
package com.jools.designpattern.create.factory.idcard;

public class IDCardFactory extends Factory {

    //保存账户人名
    private List<String> owners = new ArrayList<>();

    //新增: 保存账户和编号的对应
    private Map<String, String> noMapper = new HashMap<>();

    Random random = new Random();

    //新增: getter
    public Map<String, String> getNoMapper() {
        return noMapper;
    }

    @Override
    protected Product createProduct(String owner) {
        return new IDCard(owner, String.valueOf(random.nextInt(10000)));
    }

    @Override
    protected void registerProduct(Product product) {
        owners.add(((IDCard) product).getOwner());
        //注册编号 - owner 映射
        noMapper.put(((IDCard) product).getNo(), ((IDCard) product).getOwner());
    }

    //返回账户
    public List<String> getOwners() {
        return owners;
    }
}

测试

java 复制代码
package com.jools.designpattern.create.factory;

public class IDCardClient {

    public static void main(String[] args) {

        Factory factory = new IDCardFactory();
        Product card1 = factory.create("AAA");
        Product card2 = factory.create("BBB");
        Product card3 = factory.create("CCC");

        card1.use();
        card2.use();
        card3.use();

        List<String> owners = ((IDCardFactory) factory).getOwners();
        for (String name : owners) {
            System.out.println(name);
        }

        Map<String, String> noMapper = ((IDCardFactory) factory).getNoMapper();
        for (String no : noMapper.keySet()) {
            System.out.println("编号:" + no + " - 用户:" + noMapper.get(no));
        }
        /*
            制作AAA 的 ID 卡 ------  编号:2694
            制作BBB 的 ID 卡 ------  编号:7127
            制作CCC 的 ID 卡 ------  编号:5388
            使用了AAA 的 ID 卡
            使用了BBB 的 ID 卡
            使用了CCC 的 ID 卡
            AAA
            BBB
            CCC
            编号:7127 - 用户:BBB
            编号:5388 - 用户:CCC
            编号:2694 - 用户:AAA
         */
    }
}

总结


使用场景

  1. 客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可
  2. 创建对象的任务委托给多个工厂子类中的一个,客户端无须关心哪一个工厂的子类创建产品

工厂模式的本质

延迟到子类来选择实现

解决的问题

  1. 解决简单工厂模式的缺点:工厂需要生产新的产品不需要再修改工厂类中的对应逻辑,符合 "开闭" 原则
  2. 具体的产品创建延迟到了工厂类的子类中,此时工厂类不再负责所有产品的创建。

体现的设计模式原则

  1. 开闭原则:只增加一种产品的时候,只需要增加相应的具体产品类的相应的工厂子类即可
  2. 单一职责:每个具体工厂类只负责创建对应的产品
  3. 封装性和扩展性:保留了简单工厂的封装优点,提升了扩展性
  4. 依赖倒置:高层组件不应依赖低层组件,二者都应依赖抽象。倒置的是这个接口的 "所有权",底层实现的接口的所有权并不在底层组件上,而是倒置到高层组件中去

存在缺陷

  1. 耦合度:添加新产品类需提供对应工厂类,系统类个数成对增加,提升系统复杂度。
  2. 引入了抽象层,增加了系统复杂度
  3. 工厂方法保证内部修改关闭,但使用工厂方法的类若更换产品,仍需修改实例化的具体工厂类。

何时选用工厂模式?

  • 如果一个类需要创建某个接口的对象,但是又不知道具体的实现,这种情况可以选用工厂方法模式,把创建对象的工作延迟到子类去实现。
  • 如果一个类本身就希望,由它的子类来创建所需的对象的时候,应该使用工厂方法模式。
  • 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用的时候可以无无须关系哪个工厂子类创建产品子类,需要时再动态指定

更多案例


场景问题 01

考虑 实现导出数据应用框架,供客户选导出方式并执行 。企业系统常分散,公司无专网实力,不愿数据实时在广域网传,因安全和速度。有折中方案,分公司系统独立在局域网运行,每日业务结束导出数据传总公司或专人送。系统导出有约定方式,如文本、数据库备份、Excel、Xml 格式 等。现考虑实现此框架。

解决场景问题

java 复制代码
/**
 * 导出的文件对象的接口
 */
public interface ExportFileApi {
   /**
    * 导出内容成为文件
    * @param data 示意:需要保存的数据
    * @return 是否导出成功
    */
   public boolean export(String data);
}

对于实现导出数据的业务功能对象,它需要创建ExportFileApi的具体实例对象,但是它只知道ExportFileApi接口,而不知道其具体的实现。

工厂方法的定义

定义一个用于创建对象的接口,让子类决定实例化哪一个类, Factory Method使得一个类的实例化延迟到子类

应用工厂方法模式的思路

在实现导出数据的业务功能对象中,不知要用哪种导出文件格式,所以不应与具体导出文件对象耦合,只需面向其接口。

问题:接口是不能直接使用的,需要使用具体的接口实现对象的实例

定义方法创建,它不知如何创建接口对象,定义成抽象方法让子类实现,这样对象可面向接口编程,无需关心如何创建接口对象。

简化结构图

解释

  1. Product: 定义工厂方法所创建的对象的接口,实际需要使用的对象的接口
  2. ConcreteProduct: 具体的 Product 接口的实现对象
  3. Creator: 创建器,声明工厂方法,工厂方法通常会返回一个 Product类型的实例对象,而且多是抽象方法。也可以在 Creator 里面提供工厂方法的默认实现,让工厂方法返回一个缺省的 Product
  4. ConcreteCreator: 具体的创建器对象,覆盖实现 Creator 定义的工厂方法,返回具体的 Product实例

使用工厂模式实现解决方法

定义抽象 ExportFileApi

java 复制代码
/**
 * @description: 导出的文件对象的接口
 */
public interface ExportFileApi {
    
    boolean export(String data);
}

接口 ExportFileApi 的具体实现

java 复制代码
public class ExportTxtFile implements ExportFileApi{
    @Override
    public boolean export(String data) {
        System.out.println("导出数据:" + data + " 到文本文件内");
        return true;
    }
}

public class ExportDB implements ExportFileApi{
    @Override
    public boolean export(String data) {
        System.out.println("导出数据" + data + " 到数据库进行备份");
        return true;
    }
}

定义抽象接口 ExportOperate

java 复制代码
public abstract class ExportOperate {

    /**
     * 导出文件
     */
    public boolean export(String data) {
        //使用工厂方法
        ExportFileApi exportFileApi = factoryMethod();
        return exportFileApi.export(data);
    }

    protected abstract ExportFileApi factoryMethod();
}

Creator 的具体实现类

java 复制代码
public class ExportTextFileOperate extends ExportOperate {
    @Override
    protected ExportFileApi factoryMethod() {
        //创建导出成文本文件格式的对象
        return new ExportTxtFile();
    }
}


public class ExportDBOperate extends ExportOperate {
    @Override
    protected ExportFileApi factoryMethod() {
        //创建导出数据库备份文件形式的对象
        return new ExportDB();
    }
}

客户端可以基于需要使用的 Creator 对象,调用相应的方法

java 复制代码
public class Client {

    public static void main(String[] args) {

        ExportOperate operate = new ExportDBOperate();

        //Operate 抽象类中 export 方法调用 factoryMethod 返回 ExportFileApi 接口实现类完成
        operate.export("测试数据");
        
        /*
        输出: 导出数据测试数据 到数据库进行备份
         */
    }
}

时序图

01 - 客户端使用 Creator 创建出来的对象情况的调用给顺序示意图

02 - 客户端使用 Creator 对象的时候的调用顺序

依赖注入和控制反转是同一个概念吗?

不是,描述的角度不一致

  • 依赖注入是从应用程序的角度在描述
  • 而控制反转是从容器的角度在描述

小结:工厂方法模式与 IoC/DI 思想在思想层面相似,皆为 主动变被动 进行了 主从换位,以获更灵活程序结构。

相关推荐
tekin3 分钟前
Go、Java、Python、C/C++、PHP、Rust 语言全方位对比分析
java·c++·golang·编程语言对比·python 语言·php 语言·编程适用场景
Vitalia8 分钟前
从零开始学 Rust:基本概念——变量、数据类型、函数、控制流
开发语言·后端·rust
李长渊哦1 小时前
Java 虚拟机(JVM)方法区详解
java·开发语言·jvm
陌殇殇2 小时前
002 SpringCloudAlibaba整合 - Feign远程调用、Loadbalancer负载均衡
java·spring cloud·微服务
猎人everest3 小时前
SpringBoot应用开发入门
java·spring boot·后端
山猪打不过家猪5 小时前
ASP.NET Core Clean Architecture
java·数据库·asp.net
AllowM5 小时前
【LeetCode Hot100】除自身以外数组的乘积|左右乘积列表,Java实现!图解+代码,小白也能秒懂!
java·算法·leetcode
不会Hello World的小苗6 小时前
Java——列表(List)
java·python·list
二十七剑7 小时前
jvm中各个参数的理解
java·jvm
网络安全(king)8 小时前
网络安全知识:网络安全网格架构
安全·web安全·架构