设计模式_创建型模式 -《工厂模式》

news2025/1/20 2:00:06

设计模式_创建型模式 -《工厂模式》

笔记整理自 黑马程序员Java设计模式详解, 23种Java设计模式(图解+框架源码分析+实战)

概述

需求:设计一个咖啡店点餐系统。

  • 设计一个咖啡类(Coffee),并定义其两个子类(美式咖啡【AmericanCoffee】和拿铁咖啡【LatteCoffee】);
  • 再设计一个咖啡店类(CoffeeStore),咖啡店具有点咖啡的功能。

具体类的设计如下:

在 java 中,万物皆对象,这些对象都需要创建,如果创建的时候直接 new 该对象,就会对该对象耦合严重,假如我们要更换对象,所有 new 对象的地方都需要修改一遍,这显然违背了软件设计的开闭原则。如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的;所以说,工厂模式 (Factory Pattern) 最大的优点就是:解耦

在本教程中会介绍三种工厂的使用:

  • 简单工厂模式(不属于 GOF 的 23 种经典设计模式)
  • 工厂方法模式
  • 抽象工厂模式

简单工厂模式

简单工厂不是一种设计模式,反而比较像是一种编程习惯。

结构

简单工厂包含如下角色:

  • 抽象产品:定义了产品的规范,描述了产品的主要特性和功能。
  • 具体产品:实现或者继承抽象产品的子类
  • 具体工厂:提供了创建产品的方法,调用者通过该方法来获取产品。

实现

现在使用简单工厂对上面案例进行改进,类图如下:

  • 工厂类代码如下:

    /**
     * 简单咖啡工厂类,用来生产咖啡
     */
    public class SimpleCoffeeFactory {
    
        public Coffee createCoffee(String type) {
            Coffee coffee = null;
            if ("americano".equals(type)) {
                coffee = new AmericanoCoffee();
            } else if ("latte".equals(type)) {
                coffee = new LatteCoffee();
            }
            return coffee;
        }
    }
    
  • 咖啡店类:

    public class CoffeeStore {
    
        public Coffee orderCoffee(String type) {
    		// 创建简单工厂
            SimpleCoffeeFactory factory = new SimpleCoffeeFactory();
            // 调用生产咖啡的方法
            Coffee coffee = factory.createCoffee(type);
    
            // 加配料
            coffee.addMilk();
            coffee.addsugar();
    
            return coffee;
        }
    }
    

工厂(factory)处理创建对象的细节,一旦有了 SimpleCoffeeFactory,CoffeeStore 类中的 orderCoffee() 就变成此对象的客户,后期如果需要 Coffee 对象直接从工厂中获取即可。这样也就解除了和 Coffee 实现类的耦合,同时又产生了新的耦合,CoffeeStore 对象和 SimpleCoffeeFactory 工厂对象的耦合,工厂对象和商品对象的耦合。

后期如果再加新品种的咖啡,我们势必要需求修改 SimpleCoffeeFactory 的代码,违反了开闭原则。工厂类的客户端可能有很多,比如创建美团外卖等,这样只需要修改工厂类的代码,省去其他的修改操作。

优缺点

优点

  • 封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层分开,这样以后就避免了修改客户代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展。

缺点

  • 增加新产品时还是需要修改工厂类的代码,违背了“开闭原则”。

扩展

静态工厂

  • 在开发中也有一部分人将工厂类中的创建对象的功能定义为静态的,这个就是静态工厂模式,它也不是 23 种设计模式中的。代码如下:
public class SimpleCoffeeFactory {

    public static Coffee createCoffee(String type) {
        Coffee coffee = null;
        if ("americano".equals(type)) {
            coffee = new AmericanoCoffee();
        } else if("latte".equals(type)) {
            coffee = new LatteCoffee();
        }
        return coffe;
    }
}

工厂方法模式

针对上例中的缺点,使用工厂方法模式就可以完美的解决,完全遵循开闭原则。

概念

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

结构

工厂方法模式的主要角色:

  • 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
  • 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
  • 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
  • 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。

实现

使用工厂方法模式对上例进行改进,类图如下:

代码如下

  • 抽象工厂:

    public interface CoffeeFactory {
    	// 创建咖啡对象的方法 由具体的子类实现
        Coffee createCoffee();
    }
    
  • 具体工厂:

    // 拿铁咖啡工厂,专门用来生产拿铁咖啡
    public class LatteCoffeeFactory implements CoffeeFactory {
    
        public Coffee createCoffee() {
            return new LatteCoffee();
        }
    }
    
    // 美式咖啡工厂,专门用来生产拿式咖啡
    public class AmericanCoffeeFactory implements CoffeeFactory {
    
        public Coffee createCoffee() {
            return new AmericanCoffee();
        }
    }
    
  • 咖啡店类:

    public class CoffeeStore {
    
        private CoffeeFactory factory;
    
        public CoffeeStore(CoffeeFactory factory) {
            this.factory = factory;
        }
    
        public Coffee orderCoffee() {
            Coffee coffee = factory.createCoffee();
            coffee.addMilk();
            coffee.addsugar();
            return coffee;
        }
    }
    

从以上的编写的代码可以看到,要增加产品类时也要相应地增加工厂类,不需要修改工厂类的代码了,这样就解决了简单工厂模式的缺点。

工厂方法模式是简单工厂模式的进一步抽象。由于使用了多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。

优缺点

优点

  • 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
  • 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则;

缺点

  • 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。

抽象工厂模式

前面介绍的工厂方法模式中考虑的是一类产品的生产,如畜牧场只养动物、电视机厂只生产电视机、传智播客只培养计算机软件专业的学生等。

这些工厂只生产同种类产品,同种类产品称为同等级产品,也就是说:工厂方法模式只考虑生产同等级的产品,但是在现实生活中许多工厂是综合型的工厂,能生产多等级(种类) 的产品,如电器厂既生产电视机又生产洗衣机或空调,大学既有软件专业又有生物专业等。

本节要介绍的抽象工厂模式将考虑多等级产品的生产,将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族,下图所示横轴是产品等级,也就是同一类产品;纵轴是产品族,也就是同一品牌的产品,同一品牌的产品产自同一个工厂。

概念

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

抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。

结构

抽象工厂模式的主要角色如下:

  • 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品。
  • 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
  • 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
  • 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它 同具体工厂之间是多对一的关系。

实现

现咖啡店业务发生改变,不仅要生产咖啡还要生产甜点,如提拉米苏、抹茶慕斯等,要是按照工厂方法模式,需要定义提拉米苏类、抹茶慕斯类、提拉米苏工厂、抹茶慕斯工厂、甜点工厂类,很容易发生类爆炸情况。其中拿铁咖啡、美式咖啡是一个产品等级,都是咖啡;提拉米苏、抹茶慕斯也是一个产品等级;拿铁咖啡和提拉米苏是同一产品族(也就是都属于意大利风味),美式咖啡和抹茶慕斯是同一产品族(也就是都属于美式风味)。所以这个案例可以使用抽象工厂模式实现。类图如下:

代码如下

  • 抽象工厂:

    public interface DessertFactory {
    
        Coffee createCoffee();
    
        Dessert createDessert();
    }
    
  • 具体工厂:

    // 美式甜点工厂
    public class AmericanDessertFactory implements DessertFactory {
    
        public Coffee createCoffee() {
            return new AmericanCoffee();
        }
    
        public Dessert createDessert() {
            return new MatchaMousse();
        }
    }
    
    // 意大利风味甜点工厂
    public class ItalyDessertFactory implements DessertFactory {
    
        public Coffee createCoffee() {
            return new LatteCoffee();
        }
    
        public Dessert createDessert() {
            return new Tiramisu();
        }
    }
    

如果要加同一个产品族的话,只需要再加一个对应的工厂类即可,不需要修改其他的类。

优缺点

优点

  • 当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。

缺点

  • 当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。这有点违反了对修改关闭,对扩展开放这个设计原则。

使用场景

  • 当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等。
  • 系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。
  • 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。

如:

  • 输入法换皮肤,一整套一起换。
  • 生成不同操作系统的程序。

模式扩展

简单工厂 + 配置文件解除耦合

可以通过工厂模式 + 配置文件的方式解除工厂对象和产品对象的耦合。在工厂类中加载配置文件中的全类名,并创建对象进行存储,客户端如果需要对象,直接进行获取即可。

  • 第一步:定义配置文件

    为了演示方便,我们使用 properties 文件作为配置文件,名称为 bean.properties

    american=com.itheima.pattern.factory.config_factory.AmericanCoffee
    latte=com.itheima.pattern.factory.config_factory.LatteCoffee
    
  • 第二步:改进工厂类

    public class CoffeeFactory {
    
        /*
         * 加载配置文件,获取配置文件中配置的全类名,并创建该类的对象进行存储
         */
        // 1.定义容器对象存储咖啡对象
        private static Map<String, Coffee> map = new HashMap();
    
        // 2.加载配置文件,只需要加载一次
        static {
            // 2.1 创建Properties对象
            Properties p = new Properties();
            // 2.2 调用p对象中的load方法进行配置文件的加载
            InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            try {
                p.load(is);
                // 遍历Properties集合对象
                Set<Object> keys = p.keySet();
                for (Object key : keys) {
                    // 根据键获取值(全类名)
                    String className = p.getProperty((String) key);
                    // 获取字节码对象
                    Class clazz = Class.forName(className);
                    // 通过反射技术创建对象
                    Coffee obj = (Coffee) clazz.newInstance();
                    // 将名称和对象存储到容器中
                    map.put((String) key, obj);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        // 根据名称获取对象
        public static Coffee createCoffee(String name) {
            return map.get(name);
        }
    }
    

静态成员变量用来存储创建的对象(键存储的是名称,值存储的是对应的对象),而读取配置文件以及创建对象写在静态代码块中,目的就是只需要执行一次。

JDK源码解析-Collection.iterator方法

public class Demo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("令狐冲");
        list.add("风清扬");
        list.add("任我行");

        // 获取迭代器对象
        Iterator<String> it = list.iterator();
        // 使用迭代器遍历
        while(it.hasNext()) {
            String ele = it.next();
            System.out.println(ele);
        }
    }
}

对上面的代码大家应该很熟,使用迭代器遍历集合,获取集合中的元素。而单列集合获取迭代器的方法就使用到了工厂方法模式。我们看通过类图看看结构:

Collection 接口是抽象工厂类,ArrayList 是具体的工厂类;Iterator 接口是抽象产品类,ArrayList 类中的 Itr 内部类是具体的产品类。在具体的工厂类中 iterator() 方法创建具体的产品类的对象。

image-20230103150804515

另:

  • DateForamt 类中的 getInstance() 方法使用的是工厂模式;
  • Calendar 类中的 getInstance() 方法使用的是工厂模式;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/155216.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL学习之一条SQL查询语句的执行

文章目录前言一、MySQL基础架构二、连接器三、查询缓存四、分析器五、优化器六、执行器前言 今天我们通过一条SQL查询语句的执行过程&#xff0c;来剖析MySQL的基础架构。让我们从宏观上先有一个对MySQL的认识与了解 一、MySQL基础架构 首先&#xff0c;我们要对MySQL的整体架…

深入分析Linux PCI驱动框架分析(二)

说明&#xff1a; Kernel版本&#xff1a;4.14ARM64处理器使用工具&#xff1a;Source Insight 3.5&#xff0c; Visio 1. 概述 本文将分析Linux PCI子系统的框架&#xff0c;主要围绕Linux PCI子系统的初始化以及枚举过程分析&#xff1b;如果对具体的硬件缺乏了解&#xff…

详解c++---vector介绍

这里写目录标题什么是vectorvector的定义reservevector数据插入push_backinsertresizeassignvector数据的删除pop_backeraseclearvector性质查看sizecapacityemptymax_sizevector元素修改operator[ ]atfrontbackvector其他函数operatorswap什么是vector vector是表示可变大小数…

深入浅出Cookie、Session、Token:背后的技术原理

目录 简介 . 网站交互体验升级 1.1 无状态的 HTTP 协议 1.2 解决之道 2. Cookie方案 2.1 Cookie 定义和作用 2.2 服务端创建 Cookie 2.4 存在的问题 3. Session 方案 3.1 Session 机制的概念 3.2 简单的交互流程 3.3 Session 的实现方式 3.4 存在的问题 4. Token…

【微服务】Nacos 认证机制

目录 一、背景 二、需求 三、方案 1、安全架构选型 2、会话管理 2.1、会话选型 2.2、Session 登录流程 2.3、Token 登录流程 2.4、jwt 框架选型 2.5、会话超时 3、SSO 支持 4、UI设计 5、接口设计 6、数据库表设计 6.1、user表 6.2、roles表 7、Filter 拦截请求…

C++ —— 模板的基本概念和使用

目录 1.函数模板是什么 1.1函数模板的基本概念 1.2函数模板的基本使用 1.3函数模板的特化 1.4非类型模板参数 2.类模板是什么 2.1类模板的基本使用 2.2非类型模板参数 2.3类模板的特化 2.4模板特化后的优先级 3.函数模板不要分离编译 1.函数模板是什么 模板是一种…

python和MySQL的基础使用和数据的插入导出

一.基础使用第三方库pymysql除了使用图形化工具以外&#xff0c;我们也可以使用编程语言来执行SQL从而操作数据库。在Python中&#xff0c;使用第三方库:pymysql来完成对MySQL数据库的操作。安装创建到MySQL的数据库链接具体代码如下from pymysql import Connect #获取到MySQL数…

LVS+Keepalived+Nginx宏观整体结构与关键问答

视频链接&#xff1a;4-2 为什么要使用 LVS Nginx&#xff1f;_哔哩哔哩_bilibili ———————————————————————————————————————————————————————— 1. 问题&#xff1a; 为什么要使用LVS Nginx&#xff1f;&#xf…

C语言刮刮乐(掩码图的范例)

程序简介 这个程序模拟了刮刮乐的刮卡操作&#xff0c;按下鼠标左键并移动可以刮开刮卡层。 刮卡操作是通过掩码图实现的&#xff0c;一张隐藏的待刮开背景图&#xff0c;一张掩码图。 刮卡的时候&#xff0c;是在黑色的掩码图上画线&#xff0c;显示的时候&#xff0c;通过…

官方正品 | Ultralytics YOLOv8算法来啦(尖端SOTA模型)

&#x1f680;&#x1f680;&#x1f680;卷王之王 | Ultralytics YOLOv8 算法来啦&#xff01;&#xff01;✨✨✨ 一、前言简介 &#x1f384;&#x1f388; &#x1f4da; 代码地址&#xff1a;卷王之王 | YOLOv8代码下载地址 &#x1f4da; 详细文档&#xff1a;https://…

代码随想录算法训练营第十四天字符串 java :二叉树理论基础 144前序遍历 145后续遍历94 中序遍历

系列文章目录 第十一天笔记 文章目录系列文章目录前言1、二叉树理论基础1.1二叉树的种类1.1 如何区分二叉树的遍历方式1.2 如何定义二叉树节点2 递归遍历2.1**前序遍历 AC代码**2.2**后序遍历 AC代码**2.3 **中序遍历 AC代码**3 迭代法4 层次遍历总结**什么是List<List <…

组态王软件与S7-1200无线MODBUS通信方案详解

本方案是组态软件与西门子 S7-1200进行无线 MODBUS 通信的实现方法。此方案可以作为西门子 S7-1200与组态软件的无线 MODBUS 通信实例。在本方案中采用了西门子PLC专用无线通讯终端DTD434MC&#xff0c;作为实现无线通讯的硬件设备。 一、方案概述 组态王配置为标准 MODBUS 主…

基础面试问题

在Java中获取当前的工作目录System.getProperty("user.dir")public class Test {public static void main(final String[] args) {final String dir System.getProperty("user.dir");System.out.println("current dir " dir);} }获取一定范围…

Redis01之Windows版本的Redis安装配置

目录 0. 学习网址 https://www.w3cschool.cn/redis/https://www.w3cschool.cn/redis/ 1. Redis简介 2. 下载 3. 安装和配置 3.1 window(略...) 3.2 linux(CentOS) 4. Redis支持五种数据类型 5.通过命令操作redis 0. 学习网址 https://www.w3cschool.cn/redis/http…

一文搞懂CPU如何控制I/O设备

1 接口和设备&#xff1a;经典适配器模式 输入输出设备不只是一个设备。大部分输入输出设备&#xff0c;都有&#xff1a; 它的接口&#xff08;Interface&#xff09;实际的I/O设备&#xff08;Actual I/O Device&#xff09; 硬件设备并非直接接入到总线上和CPU通信&#…

UOS 录制电脑播放的音频 / 内录音频

Windows 里面有一个“立体声混音”&#xff0c;可以内录电脑播放的音频&#xff0c;而不受到外界噪音的干扰。前段时间接到反馈说 UOS 的设置里面的音频输入里面没有可以选择的设备&#xff0c;这里就稍微探索了一下&#xff0c;发现 UOS 也是可以配置内录的。这里参考了一下这…

网络基础(一)

网络基础&#xff08;一&#xff09;计算机网络背景网络发展独立模式: &#xff08;计算机之间相互独立&#xff09;网络互联: ( 多台计算机连接在一起, 完成数据共享)局域网LAN: (计算机数量更多了, 通过交换机和路由器连接在一起);广域网WAN: &#xff08;将远隔千里的计算机…

vue入门到精通(一)

一、vue简介 Vue是一款用于构建用户界面的 JavaScript 框架。 它基于标准 HTML、CSS 和 JavaScript 构建&#xff0c;并提供了一套声明式的、组件化的编程模型&#xff0c;帮助你高效地开发用户界面。 无论是简单还是复杂的界面&#xff0c;Vue 都可以胜任。 二、vue3选项式…

Linux编译器-gcc/g++使用

目录 1. 背景知识 2. gcc如何完成 2.1 预处理(进行宏替换) 2.2 编译&#xff08;生成汇编&#xff09; 2.3 汇编&#xff08;生成机器可识别代码&#xff09; 2.4 链接&#xff08;生成可执行文件或库文件&#xff09; 3 函数库 3.1 分类 3.2 图解 4 gcc选项 1. 背景知…

开源工具系列2:Trivy

在云原生安全的场景中&#xff0c;一个常见的场景就是对漏洞和配置进行扫描&#xff0c;以发现整个 K8s 环境的安全问题。今天我们来介绍一个高效的扫描工具Trivy。 Trivy 是什么 Trivy&#xff08;tri 发音为 trigger&#xff0c;vy 发音为 envy&#xff09;是一个简单而全面…