23种设计模式-创建型模式-工厂方法
文章目录
- 简介
- 场景
- 问题
- 1. 直接依赖具体实现
- 2. 违反开闭原则
- 3. 条件分支泛滥
- 4. 代码重复风险
- 解决
- 根本问题
- 完整类图
- 完整代码
- 说明
- 核心优势
- 代码优化
- 静态配置表
- 动态策略
- 总结
简介
工厂方法是一种创建型设计模式,它提供了在父类中创建对象的接口,但允许子类改变将要创建的对象的类型。工厂方法(Factory Method)的核心目标是解耦对象创建与使用逻辑。
场景
假设你正在创建一个物流管理应用程序。物流只采用卡车运输,所以当前只能处理卡车运输,大部分代码都位于 Truck 类中。
/** 客户端运输管理类 */
class LogisticsManager {public void transportCargo() {// 直接依赖具体类 TruckTruck truck = new Truck();truck.deliver();// 新增其他运输类型需要继续扩展条件分支}
}/** 卡车类(硬编码具体实现) */
class Truck {public void deliver() {System.out.println("陆地运输:卡车配送");}
}
一段时间后,你想把海运物流也加到应用中。
这个时候你会发现,你可能需要再创建一个Ship类用于处理海运的请求,并且还需要修改主流程代码。你可能会做出如下修改:
/** 客户端运输管理类 */
class LogisticsManager {public void transportCargo(String type) {// 直接依赖具体类 Truckif ("land".equals(type)) {Truck truck = new Truck();truck.deliver();} else if ("sea".equals(type)) {Ship ship = new Ship(); // 新增 Ship 需修改此处ship.deliver();}// 新增其他运输类型需要继续扩展条件分支}
}/** 卡车类(硬编码具体实现) */
class Truck {public void deliver() {System.out.println("陆地运输:卡车配送");}
}/** 新增船舶类时被迫修改既有代码 */
class Ship {public void deliver() {System.out.println("海运:船舶配送");}
}
问题
1. 直接依赖具体实现
LogisticsManager 直接通过 new Truck() 硬编码创建对象,客户端代码与 Truck 类强耦合。新增 Ship 类时必须修改 transportCargo 方法的逻辑。
2. 违反开闭原则
每添加一种新的运输工具(例如 Ship 或 Airplane),都需要:
- 修改 transportCargo 方法,增加条件分支
- 引入对其他具体类的依赖
- 导致核心业务逻辑频繁变更
3. 条件分支泛滥
if-else 或 switch 语句扩散到多个位置(例如支付方式、日志记录等其他模块重复出现类似代码),维护成本指数级增长。
4. 代码重复风险
若其他模块(如计费系统)也需要根据运输类型创建对象,必须重复编写相同的条件分支逻辑,系统复杂度失控。
解决
根本问题
对象创建逻辑与业务逻辑深度耦合,未遵循「面向接口编程」原则 。工厂方法模式通过把创建过程封装到独立层级(如新增 LandLogistics / SeaLogistics 子类)可彻底解决这些问题。
我们用工厂方法代替直接调用对象构造函数(也就是使用new)。但是对象还是通过new创建,只是new变成了在工厂方法里调用。工厂方法返回的对象就是“产品”。

粗略看着这种更改可能没啥意义,我们只是改变了程序中调用构造函数的位置而已。但是,仔细想一下,现在你可以在子类中重写工厂方法,从而改变它创建产品的类型。
但有一点需要注意:只有这些产品具有共同的基类或者接口时,子类才能返回不同类型的产品,同时基类中工厂方法的返回类型也要声明成这个共有接口。

举例来说,卡车Truck和轮船Ship类都必须实现运输Transport接口,这个接口声明了一个deliver方法。每个类都会以不同的方式实现这个方法:卡车走陆路交付货物,轮船走海路交付货物。陆路运输RoadLogistics类里的工厂方法返回卡车对象,而海路运输SeaLogistics类就返回轮船对象。
完整类图

完整代码
// 抽象产品接口:所有运输工具必须实现此接口 [^6]
interface Transport {void deliver();
}// 具体产品实现:卡车类
class Truck implements Transport {@Overridepublic void deliver() {System.out.println("陆地运输:卡车配送");}
}// 具体产品实现:船舶类
class Ship implements Transport {@Overridepublic void deliver() { System.out.println("海运:船舶配送");}
}// 抽象工厂类(核心工厂方法) [^6]
abstract class Logistics {// 模板方法:组合核心逻辑与工厂方法public void planDelivery() {Transport transport = createTransport();transport.deliver();}// 工厂方法声明:延迟具体运输工具创建到子类public abstract Transport createTransport();
}// 具体工厂类:陆地物流 [^6]
class RoadLogistics extends Logistics {@Overridepublic Transport createTransport() {return new Truck(); // 无需判读条件的直接构造}
}// 具体工厂类:海洋物流
class SeaLogistics extends Logistics {@Overridepublic Transport createTransport() {return new Ship();}
}// 客户端调用示例
class Client {public void clientMethod(String logisticsType) {Logistics logistics;// 通过简单映射选择具体工厂if ("road".equals(logisticsType)) {logistics = new RoadLogistics();} else {logistics = new SeaLogistics(); }// 统一调用接口(核心业务逻辑无需修改)logistics.planDelivery();}
}
说明
- 实现关系:Truck/Ship 实现 Transport 接口
- 继承关系:RoadLogistics/SeaLogistics 继承 Logistics 抽象类
- 依赖关系:Logistics 依赖 Transport 接口(虚线箭头)
核心优势
- 快速扩展能力:新增空运只需添加 AirLogistics 类和 Plane 产品类,零/少修改现有代码
- 核心业务稳定:planDelivery() 模板方法与运输工具实现完全解耦
- 统一接口调用:客户端通过 Logistics 基类操作所有物流类型,符合里氏替换原则(在使用继承的过程中,子类可以完全替换掉父类,并且软件的功能不受到影响。这个原则是保证代码的健壮性和可扩展性的重要原则之一)
代码优化
以上代码在客户端中还是存在条件判断分支,可以通过静态配置注册表或者动态策略解决。如果新增运输工具,客户端就只需要修改配置文件即可,真正实现零修改现有代码。
静态配置表
// 创建对象注册表
class LogisticsRegistry {private static Map<String, Supplier<Logistics>> registry = new HashMap<>();static {registry.put("road", RoadLogistics::new); // 配置化绑定, 可以从配置文件中加载registry.put("sea", SeaLogistics::new);}public static Logistics getLogistics(String type) {return registry.getOrDefault(type, SeaLogistics::new).get();}
}// 客户端无需任何条件判断
class Client {public void clientMethod(String logisticsType) {Logistics logistics = LogisticsRegistry.getLogistics(logisticsType);logistics.planDelivery();}
}
动态策略
若系统存在动态运行时判定逻辑(如根据GPS坐标实时选择最优运输方式),则需在简单工厂层用策略模式处理:
class DynamicLogisticsSelector {public Logistics selectByConditions(Context ctx) {// 动态策略分支(仍可避免硬编码if)return strategyMap.get(ctx.analyze()).create();}
}
总结

- 产品(Product)接口。对于所有由创建者(Creator)以及它的子类构建的对象,这些接口都是通用的。
- 具体产品(Concrete Products)是产品接口的不同实现。
- 创建者(Creator)声明了返回产品对象的工厂方法。方法的返回对象类型必须与产品接口一致。你可以把工厂方法声明为抽象方法,强制要求每个子类重新实现。或者,你也可以在基类工厂方法中返回默认产品类型。
- 具体创建者(Concrete Creators)会重写基础的工厂方法,让他返回不同类型的产品。注意,并不一定每次调用工厂方法都会创建新的实例。工厂方法也可以返回缓存、对象池或其他来源的已有对象。
相关文章:
23种设计模式-创建型模式-工厂方法
文章目录 简介场景问题1. 直接依赖具体实现2. 违反开闭原则3. 条件分支泛滥4. 代码重复风险 解决根本问题完整类图完整代码说明核心优势代码优化静态配置表动态策略 总结 简介 工厂方法是一种创建型设计模式,它提供了在父类中创建对象的接口,但允许子类…...
142. 环形链表 II——考察数学,难!
142. 环形链表 IIhttps://leetcode.cn/problems/linked-list-cycle-ii/ 给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,…...
C++常见问题与思考
TLS(线程本地存储)原理 线程本地存储(Thread Local Storage,TLS)是一种机制,它允许每个线程拥有自己独立的变量实例,这些变量的生命周期与线程相同。也就是说,不同线程对同一个 TLS…...
从零开始:使用Luatools工具高效烧录Air780EPM核心板项目的完整指南
本文将深入讲解如何使用Luatools工具烧录一个具体的项目到Air780EPM开发板中。如何使用官方推荐的Luatools工具(一款跨平台、命令行驱动的烧录利器),通过“环境配置→硬件连接→参数设置→一键烧录”四大步骤,帮助用户实现Air780E…...
关于c++的几个简单算法
一. 动态规划(Dynamic Programming) 难点:状态转移方程的构建和初始化条件的设计 典型问题:01背包问题 分析: 状态定义 dp[i][j] 表示前i个物品放入容量为j的背包的最大价值。状态转移需要判断是否选择当前物品。 #i…...
WPF MergedDictionaries详解
在 WPF 中,ResourceDictionary.MergedDictionaries 是一个非常重要的特性,用于将多个资源字典(ResourceDictionary)合并到一个主资源字典中。这种机制使得资源的管理和复用变得更加灵活和高效。 1. MergedDictionaries 的作用 Me…...
一套云HIS系统源码,系统融合HIS与EMR,基于云端部署,采用B/S架构与SaaS模式
云HIS系统完全基于云端部署,采用B/S架构,并通过软件即服务(SaaS)的形式面向二级及以下医院可快速交付、便捷运维、云化的医院核心业务平台产品。融合医院HIS和EMR两大主营系统,构建涵盖患者、费用、医嘱、电子病历等核…...
DisplayPort(DP)详解
一、DisplayPort的定义与核心特性 DisplayPort(DP) 是由 视频电子标准协会(VESA) 制定的 高性能数字音视频接口,专为高分辨率显示器和多屏应用设计。其核心特性包括: 高带宽:DisplayPort 2.0支…...
C++数据结构(搜索二叉树)
1.二叉树搜索的概念 二叉搜索数也成为二叉排序树,它或者是一颗空树,或者是满足以下性质的树: 1.若他的左子树不为空,则左子树上的所有节点的值都小于等于根节点的值。 2.若他的右子树不为空,则右子树上的所有节点的值…...
OpenCV图像拼接(6)图像拼接模块的用于创建权重图函数createWeightMap()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 cv::detail::createWeightMap 是 OpenCV 库中用于图像拼接模块的一个函数,主要用于创建权重图。这个权重图在图像拼接过程中扮演着重…...
Micropython RPI-PICO 随记-双PICO串口传数据
开发环境 MCU:双 Pico1(无wifi版),串口相连,需要共地使用固件:自编译版本开发环境:MacBook Pro Sonoma 14.5开发工具:Thonny 4.1.6开发语言:MicroPython 1.24.0 上位机…...
炫酷的HTML5粒子动画特效实现详解
炫酷的HTML5粒子动画特效实现详解 这里写目录标题 炫酷的HTML5粒子动画特效实现详解项目介绍技术栈项目架构1. HTML结构2. 样式设计 核心实现1. 粒子类设计2. 动画效果实现星空效果烟花效果雨滴效果 3. 鼠标交互 性能优化效果展示总结 项目介绍 本文将详细介绍如何使用HTML5 C…...
YoloV8训练和平精英人物检测模型
概述 和平精英人物检测,可以识别游戏中所有人物角色,并通过绘制框将人物选中,训练的模型仅仅具有识别功能,可以识别游戏中的视频、图片等文件,搭配Autox.js可以推理,实现实时绘制,但是对手机性…...
BC93 公务员面试
🚀个人主页:BabyZZの秘密日记 📖收入专栏:C语言练习题分享 🌍文章目入 #include <stdio.h> int main() {int score 0, max 0, min 100, sum 0, count 0; while (scanf("%d", &score) ! EOF){…...
3.0 Disruptor的使用介绍(一)
Disruptor: 其官网定义为:“A High Performance Inter-Thread Messaging Library”,即:线程间的高性能消息框架,与Labview的生产者、消费者模型很相似。 其组成部分比较多,先介绍几个常用的概念: …...
基础实验2-2.1 整数的分类处理
基础实验2-2.1 整数的分类处理 - 浙大版《数据结构学习与实验指导(第2版)》题目集 (pintia.cn) 给定 N 个正整数,要求你从中得到下列三种计算结果: A1 能被 3 整除的最大整数A2 存在整数 K 使之可以表示为 3K1 的整数的个数A3…...
[深度学习]图像分类项目-食物分类
图像分类项目-食物分类(监督学习和半监督学习) 文章目录 图像分类项目-食物分类(监督学习和半监督学习)项目介绍数据处理设定随机种子读取文件内容图像增广定义Dataset类 模型定义迁移学习 定义超参Adam和AdamW 训练过程半监督学习定义Dataset类模型定义定义超参训练过程 项目介…...
有价值的面试问题
迅雷一面 都是c和网络问题 了解epoll吗?解释下水平触发和边缘触发,医院的叫号系统应该算哪一种 c类a有成员b,成员b调用了a的函数,但是a不小心把b的成员删除了,会发生什么,怎么解决 c类a有一个static的函数…...
禁用ONLY_FULL_GROUP_BY模式
这是由于MySQL启用了ONLY_FULL_GROUP_BY模式导致的。以下是禁用该模式的三种方法,结合你的需求选择最合适的方案: 一、临时禁用(重启后失效) 1. 当前会话禁用 直接在SQL客户端执行以下命令,仅对当前数据库连接有效&…...
SAP 获取RFC的WSDL文件
主要是CPI要用到WSDL文件做mapping,客户的SAP服务器不一定直接可在浏览器访问http或者https的地址,所以在SAP里面开发程序内部调用地址获取WSDL文件 *&---------------------------------------------------------------------* *& Report YXX_…...
SQLite优化实践
1. 启用写入批处理 使用事务将多条插入操作包装在一起,这样可以减少磁盘I/O和日志的写入。 BEGIN TRANSACTION; -- 执行多个INSERT语句 COMMIT;通过将多个插入操作包装在一个事务中,可以显著减少每次写入数据库时的磁盘I/O操作。 2. 使用更大的页大小…...
56.fm解调最简单的方法过零检测,如何确定计时器的更新速率
,...
java8循环解压zip文件---实现Excel文件数据追加
java8循环追加Excel数据 实际遇到问题:定期获取zip文件,zip文件内有几个固定模板的Excel文件,有的Excel文件可能还包含多个sheet。 有段时间一次性获取到好几个zip包,需要将这些包都解压,并且按照不同的文件名、sheet进…...
基于SpringBoot的电影售票系统
作者:计算机学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等,“文末源码”。 专栏推荐:前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏:…...
SQL Server 2022 安装问题
一、安装与配置问题 1. SQL Server 2022 安装失败怎么办? 常见原因: 硬件或操作系统不满足最低要求(如内存、磁盘空间不足)。未关闭防火墙或杀毒软件。之前版本的 SQL Server 残留文件未清理。 解决方案: 确保硬件配…...
MySQL 8.0.41安装教程(附安装包)mysql8.0.41图文详细安装教程
文章目录 前言一、MySQL 8.0.41下载安装包二、MySQL 8.0.41安装教程1.启动安装程序2.选择安装模式3.选定安装组件4.确认安装设置5.执行安装操作6.安装进行中7.设置数据库密码8.继续点击下一步9.执行配置操作10.完成配置11. 再次点击下一步12.结束安装向导 三、MySQL 8.0.41配置…...
React Router使用方法
目录 简介React Router的三种使用模式声明模式数据模式框架模式 React Router7声明模式使用方法在入口文件引入BrowserRouter配置一个路由组件管理路由将路由组件引入App.tsx嵌套路由链接式路由导航 \ 和 \<Link>编程式路由导航 简介 React Router 是 React 的多策略路由…...
2025年陕西省各市秦创原产业创新聚集区(机器人、羊乳、苹果)“四链”融合项目申报补贴要求和时间流程
征集2025年陕西省各市秦创原产业创新聚集区(机器人、羊乳、苹果)“四链”融合项目申报补贴要求和时间流程,更多详情请大家参考下文!西安市、宝鸡市、咸阳市、铜川市、渭南市、延安市、榆林市、汉中市、安康市、商洛市10市各地需要…...
深入解析 C++20 中的 std::bind_front:高效函数绑定与参数前置
文章目录 1. 什么是 std::bind_front?2. 使用 std::bind_front2.1 基本用法2.2 绑定多个参数 3. 优势与特点3.1 简化代码3.2 支持可调用对象3.3 支持完美转发 4. 实际应用场景4.1 事件处理4.2 算法通用化4.3 成员函数调用 5. 总结 在现代 C 编程中,函数绑…...
python裁剪nc文件数据
问题描述: 若干个nc文件储存全球的1850-2014年月尺度的mrro数据(或其他数据),从1850-1到2014-12一共1980个月,要提取出最后35年1980.1~2014.12年也就是420个月的数据。 代码实现 def aaa(input_file,output_file,bianliang,start_index,en…...
