php常用设计模式之工厂模式
引言
在日常开发中,我们一些业务场景需要用到发送短信通知。然而实际情况考虑到不同厂商之间的价格、实效性、可能会出现的情况等 我们的业务场景往往会接入多个短信厂商来保证我们业务的正常运行,而不同的短信厂商(如阿里云短信、腾讯云短信等)提供了不同的接口,如果我们要支持多个短信厂商,代码中就会充斥着各种 new 操作来创建不同的短信服务实例。
假设在不使用设计模式的情况下,我们的代码可能会这样写:
<?php
// 如果要发送阿里云短信
$aliSms = new AliSms();
$aliSms->send($phoneNumber, $message);// 如果要发送腾讯云短信
$tenSms = new TencentSms();
$tenSms->send($phoneNumber, $message);
这样做的问题是显而易见的:
- 代码耦合度高
每当需要引入新的短信厂商,代码中都必须手动创建该厂商的实例,导致代码与具体厂商类的耦合度非常高,不易维护。 - 扩展性差
如果要增加或替换短信厂商,就需要在每个发送短信的地方都修改代码。这不仅增加了出错的风险,还降低了代码的可扩展性。 - 不符合开闭原则
代码应该是对扩展开放、对修改封闭的。而上述代码违背了这一原则,因为每次增加新厂商时都需要修改已有代码。
为了解决这些问题,我们可以引入工厂模式,通过一个工厂类来负责短信服务的创建,从而实现代码的解耦和易扩展。
工厂模式
工厂模式(Factory Pattern)是一种创建型设计模式,它通过定义一个接口或抽象类,将对象的创建过程封装在工厂类中,而不是直接在代码中实例化对象。工厂模式的核心思想是通过工厂类来决定实例化哪一个具体类,从而使客户端代码不需要关心对象的创建逻辑。
工厂模式的原理
工厂模式的原理是将对象的创建过程封装在一个独立的工厂类中。工厂类通常根据传入的参数或配置信息,决定返回哪个具体的类实例。这样一来,客户端代码只需要调用工厂类提供的创建方法,而不需要直接调用构造函数,从而实现对象的创建与客户端的分离。
工厂模式的实现一般包含以下几部分:
- 产品接口或抽象类:定义所需的产品类型,所有的具体产品类都要实现该接口或继承该抽象类。
- 具体产品类:实现或继承产品接口或抽象类,代表具体的产品。
- 工厂类:封装创建对象的逻辑,根据客户端的需求,生成并返回对应的产品实例。
工厂模式的示意图
工厂模式的结构通常如下:
- 客户端 ——> 工厂类 ——> 产品接口/抽象类 ——> 具体产品类
这种结构将对象的创建过程从客户端代码中移除,交由工厂类处理,从而实现了创建与使用的解耦。
工厂模式的适用场景
工厂模式适用于以下几种场景:
- 需要生成多个具有相似特征的对象
如果一个系统中需要创建多个相似的对象,且这些对象具有相同的接口或抽象类,工厂模式可以将对象的创建集中管理,方便后期的扩展。 - 系统的扩展性要求较高
如果一个系统可能会频繁地扩展新功能,需要新增类型的对象,工厂模式能使新产品的引入变得简单。新增产品时只需要增加一个具体产品类和相应的工厂逻辑,而不需要修改原有的客户端代码。 - 隐藏对象的创建逻辑
如果对象的创建过程比较复杂,不希望让客户端了解创建细节,通过工厂模式可以将创建逻辑封装在工厂类中,让客户端只关心如何使用对象,而不关心如何创建对象。
工厂模式的实际业务应用场景
- 多渠道通知发送
在消息通知系统中,可能需要支持多种通知渠道,比如短信、邮件、微信、Push 推送等。工厂模式可以用于创建不同的通知服务实例,帮助根据不同渠道自动生成相应的通知对象,使得系统可以方便地切换或扩展新渠道。 - 支付渠道对接
支付系统中通常需要对接多个支付渠道,如支付宝、微信支付、PayPal 等。工厂模式可以根据用户的选择或系统配置,创建相应的支付实例,从而使系统更加灵活,方便地接入新支付渠道。 - 数据库连接管理
在复杂应用中,可能需要使用多种数据库(例如 MySQL、MongoDB、Redis 等)来处理不同的数据存储需求。工厂模式可以根据需求创建不同的数据库连接实例,使代码解耦,并方便地切换或扩展数据库支持。 - 文件解析
系统中需要解析多种文件格式(如 JSON、XML、CSV 等)时,可以使用工厂模式创建对应的解析器对象。例如,创建 JSON 解析器或 CSV 解析器,按需解析不同格式的文件。 - 日志系统
日志系统中可能会使用多种输出方式,比如文件、数据库、远程服务器等。工厂模式可以根据配置创建不同的日志对象,以支持多种日志写入方式,并能灵活地调整日志输出策略。 - 用户权限管理
对于复杂权限管理系统,不同角色可能需要不同的权限实例。工厂模式可以用于根据用户角色创建对应的权限管理对象,从而更好地管理和维护权限规则。 - 多语言支持
在多语言应用中,不同语言的翻译方式可能不同。工厂模式可以根据语言代码生成对应的翻译服务实例,以便于按需加载不同的翻译逻辑。 - 图表生成
在数据可视化系统中,可能会支持多种图表类型(如柱状图、折线图、饼图等)。工厂模式可以用于根据用户选择生成不同的图表对象,以便动态生成所需的图表类型。
在代码中使用工厂模式的好处
使用工厂模式带来了多个显著的好处,使得代码更加灵活、易维护和可扩展:
- 解耦创建和使用
工厂模式将对象的创建和使用分离,使客户端代码无需关心对象的具体实现类,也不需要直接创建实例。这种解耦使得代码更灵活,便于在不同的情况下切换不同的实现。 - 提高代码的可维护性
工厂模式将对象的创建逻辑集中在工厂类中,客户端代码只与工厂类交互。当需要添加新的对象类型或修改对象的创建逻辑时,只需更改工厂类,而无需修改客户端代码,大大减少了出错的风险。 - 符合开闭原则
工厂模式使代码更符合开闭原则(对扩展开放,对修改封闭)。如果需要添加新的产品类,只需在工厂类中增加对应的创建逻辑,而无需修改现有的客户端代码或其他产品类代码,减少了代码的修改需求。 - 增强代码的可扩展性
当需要增加新的产品类型时,只需创建一个新的产品类,并在工厂类中定义相应的创建逻辑。工厂模式使得扩展新产品变得简单,而不需要更改已有代码结构。 - 简化客户端代码
工厂模式将对象创建逻辑封装起来,简化了客户端代码,使其专注于如何使用对象,而不是关心如何创建对象。这使得代码更加清晰和易于理解。
通过这些好处,工厂模式让代码具备了更好的结构和维护性,尤其适用于需要频繁创建和扩展对象的场景。
工厂模式的类型
在工厂模式中,根据需求的不同,常见的有三种主要变体:简单工厂模式、工厂方法模式和抽象工厂模式。这些模式的主要目标都是将对象的创建与使用解耦,但在设计和使用场景上各有不同。
1. 简单工厂模式
简单工厂模式(Simple Factory Pattern)是工厂模式的基础实现,通过一个工厂类的静态方法,根据传入的参数来创建并返回不同的产品对象。这种模式使用简单,便于理解。
- 特点:通过一个静态方法来创建对象,工厂类负责所有产品实例的创建。
- 适用场景:适用于产品种类较少、对象创建逻辑简单的场景。
- 缺点:违反开闭原则,当新增产品时必须修改工厂类,系统扩展性较低。
示例代码
class SmsFactory {public static function create($type) {switch ($type) {case 'Ali':return new AliSms();case 'Tencent':return new TencentSms();default:throw new Exception("未知短信类型");}}
}
2. 工厂方法模式
工厂方法模式(Factory Method Pattern)为每个产品提供一个具体的工厂类,通过实现一个工厂接口,负责创建各自的产品。这种方式将工厂类分离,符合开闭原则。
- 特点:每个产品类都有一个对应的工厂类,通过实现工厂接口来创建具体产品。
- 适用场景:适合产品种类多、频繁扩展新产品的情况,便于维护。
- 缺点:增加了系统的复杂度,特别是当产品种类较多时,会有大量工厂类。
示例代码
interface SmsFactory {public function create();
}class AliSmsFactory implements SmsFactory {public function create() {return new AliSms();}
}class TencentSmsFactory implements SmsFactory {public function create() {return new TencentSms();}
}
3. 抽象工厂模式
抽象工厂模式(Abstract Factory Pattern)是一种更为复杂的工厂模式,用于创建多个相关的产品对象(即产品族)。每个具体工厂负责创建一组相关的对象,从而保证产品族的一致性。
- 特点:可以创建一组相关或相互依赖的对象(产品族),每个具体工厂实现多个工厂接口,负责创建整个产品族。
- 适用场景:当系统需要多个相互关联的对象组合时(例如支持多平台的 UI 组件库)。
- 缺点:扩展新产品族较为复杂,需要修改或增加多个类。
示例代码
interface SmsAbstractFactory {public function createSms();public function createTemplate();
}class AliSmsFactory implements SmsAbstractFactory {public function createSms() {return new AliSms();}public function createTemplate() {return new AliTemplate();}
}class TencentSmsFactory implements SmsAbstractFactory {public function createSms() {return new TencentSms();}public function createTemplate() {return new TencentTemplate();}
}
工厂模式类型的区别与适用场景总结
| 工厂模式类型 | 特点 | 适用场景 | 缺点 |
|---|---|---|---|
| 简单工厂模式 | 单一工厂类,通过静态方法创建对象 | 产品种类少、扩展性需求低 | 不符合开闭原则,扩展性差 |
| 工厂方法模式 | 每个产品对应一个工厂类,符合开闭原则 | 产品种类多,需支持系统扩展性 | 增加系统复杂度,工厂类较多 |
| 抽象工厂模式 | 可以创建多个相关对象(产品族),满足复杂对象创建需求 | 需要创建一组相关对象组合(产品族) | 扩展复杂,类结构较复杂 |
通过这种对比表格和示例代码,读者可以更清晰地了解不同工厂模式的特点与适用场景,并能根据项目需求选择合适的工厂模式类型。
回到开头 为了更好地管理多种短信厂商的集成,我们可以选择使用工厂模式来设计短信发送逻辑。这样一来,我们可以轻松地根据需求切换或添加新的短信厂商,而无需修改客户端代码。
设计步骤
-
定义一个短信接口:创建一个
SmsServiceInterface,定义发送短信的基本方法,比如send($phoneNumber, $message)。所有的具体短信服务(如阿里云、腾讯云)都将实现这个接口。interface SmsServiceInterface {public function send($phoneNumber, $message); } -
创建具体的短信服务类:为每个短信厂商创建具体实现类,比如
AliSmsService和TencentSmsService。每个类都实现SmsServiceInterface接口,包含具体的发送逻辑。class AliSmsService implements SmsServiceInterface {public function send($phoneNumber, $message) {// 实现阿里云短信的发送逻辑} }class TencentSmsService implements SmsServiceInterface {public function send($phoneNumber, $message) {// 实现腾讯云短信的发送逻辑} } -
创建短信工厂类:创建
SmsFactory类,根据传入的厂商类型来决定返回的短信服务实例。这样一来,客户端代码就不需要直接实例化具体的短信类,而是通过工厂类来获取相应的实例。class SmsFactory {public static function create($type) {switch ($type) {case 'Ali':return new AliSmsService();case 'Tencent':return new TencentSmsService();default:throw new Exception("未知短信类型");}} } -
使用工厂类发送短信:客户端代码只需调用工厂类的
create()方法来获取对应的短信实例,然后调用send()方法即可。这样,客户端代码无需关心具体的实现,只需使用统一的接口。$smsService = SmsFactory::create('Ali'); $smsService->send($phoneNumber, $message);
这样设计的好处
- 降低耦合性
客户端代码与具体的短信实现解耦,通过SmsFactory工厂类统一管理不同厂商的实例创建,客户端只需知道接口而不需要知道具体实现。 - 提高扩展性
使用工厂模式后,如果要接入新的短信厂商(如华为云),只需增加一个新的实现类(如HuaweiSmsService),并在工厂类中添加对应的实例创建逻辑,而无需修改客户端代码。 - 符合开闭原则
工厂模式允许我们在不修改客户端代码的情况下扩展新功能,增强了代码的灵活性和稳定性。通过简单扩展工厂类,我们便可以支持新的厂商或新的短信逻辑。 - 简化维护
所有短信实例的创建都集中在SmsFactory中,便于管理。若有修改需求,例如调整具体厂商的实现逻辑,我们只需修改对应的实现类,而不必在各处重复更改。
通过这种设计,工厂模式让短信发送逻辑更加灵活、可扩展且便于维护,为系统增加新的厂商支持变得轻松简单。这不仅提升了代码质量,也提高了系统的稳定性。
最后
通过工厂模式,我们有效地将对象的创建过程与使用逻辑解耦,使系统的扩展性和维护性得到了显著提升。在多厂商短信发送的场景中,工厂模式使得不同厂商的短信服务可以通过统一的接口进行调用,简化了代码结构。同时,工厂模式还符合开闭原则,让我们能够在不修改已有代码的前提下,轻松地接入新的厂商支持。这样的设计不仅提高了代码的灵活性和可读性,也降低了系统的耦合度,使项目更易于维护和拓展。
工厂模式在实际开发中应用广泛,尤其适用于需要动态生成对象且对象种类较多的场景。通过合理运用工厂模式,我们可以更有效地应对业务需求的变化,让系统保持稳定、可扩展的同时具备更高的质量。
相关文章:
php常用设计模式之工厂模式
引言 在日常开发中,我们一些业务场景需要用到发送短信通知。然而实际情况考虑到不同厂商之间的价格、实效性、可能会出现的情况等 我们的业务场景往往会接入多个短信厂商来保证我们业务的正常运行,而不同的短信厂商(如阿里云短信、腾讯云短信…...
通用软件版本标识
软件版本标识:了解不同的版本类型 在软件开发和发布过程中,版本号和标识扮演着重要的角色。它们不仅帮助开发者追踪软件的演变,还让用户了解软件的稳定性和功能。以下是一些常见的软件版本标识,以及它们的含义和用途。 Alpha&am…...
(计算机毕设)基于SpringBoot的就业平台开题报告
一、立题依据(国内外研究进展或选题背景、研究意义等) 国内外研究进展或选题背景 在全球化的大背景下,就业问题一直是各国政府和社会各界关注的焦点。随着互联网技术的普及和发展,网络招聘已成为求职者和企业招聘的主要渠道。据相关数据显示࿰…...
STM32G4系列MCU的ADC模块标定方法和采样时间
目录 概述 1 ADC模块标定 1.1 功能介绍 1.2 软件程序校准ADC 1.2.1 标定步骤 1.2.2 标定时序框图 1.3 软件程序重新注入校准因子到ADC 1.3.1 标定步骤 1.3.2 更新ADC校准因子 1.4 用单个ADC转换单端和差分模拟输入 1.4.1 标定流程 1.4.2 混合单端和差分通道 2 通道…...
NVIDIA Jetson支持的神经网络加速的量化平台
NVIDIA Jetson支持的神经网络加速的量化工具、技术 NVIDIA Jetson 是专为边缘计算和嵌入式系统设计的高性能计算平台,它支持多种深度学习模型的部署和推理。对于神经网络加速的量化平台,Jetson 支持以下技术和工具: TensorRT:Ten…...
MySQL 免密登录的几种配置方式
文章目录 MySQL 免密登录的几种配置方式使用操作系统用户实现免密登录具体步骤:Step 1: 修改 MySQL 配置文件Step 2: 重启 MySQL 服务Step 3: 使用系统用户登录 MySQL优点:缺点: 使用 mysql_config_editor 配置免密文件具体步骤:S…...
html全局属性、框架标签
常用的全局属性: 属性名含义id 给标签指定唯一标识,注意:id是不能重复的。 作用:可以让label标签与表单控件相关联;也可以与css、JavaScript配合使用。 注意:不能再以下HTML元素中使用:<hea…...
ARL 灯塔 | CentOS7 — ARL 灯塔搭建流程(Docker)
关注这个工具的其它相关内容:自动化信息收集工具 —— ARL 灯塔使用手册 - CSDN 博客 灯塔,全称:ARL 资产侦察灯塔系统,有着域名资产发现和整理、IP/IP 段资产整理、端口扫描和服务识别、WEB 站点指纹识别、资产分组管理和搜索等等…...
抖音列表页采集-前言
准备工作: 1.关于selenium介绍: python自动化入门的话,selenium绝对是最方便的选择,基本逻辑即为:程序模拟人的行为操作浏览器,这样的操作需要借用浏览器驱动,我选用的是chrome浏览器ÿ…...
Linux 端口占用 kill被占用的端口 杀掉端口
1、yum install lsof 2、输入netstat -tln,查看系统当前所有被占用端口 3、根据端口查询进程,输入lsof -i :9555,切记不要忘了添加冒号 4、 既然知道进程号了,那杀死当前进程就简单多了,直接 kill -9 PID 回车...
爬虫之数据解析
数据解析 数据解析这篇内容, 很多知识涉及到的都是以前学习过的内容了, 那这篇文章我们主要以实操为主, 来展开来讲解关于数据解析的内容。 360搜索图片 请求的url大家不需要再找了, 相信大家都会找请求了, 寻找请求从我的第一篇爬虫的博客开始到现在一直都在写,这边的话, 我已…...
本地缓存少更改、小数据、低一致表的思考
对于那些少更改、小数据的表,以及对一致性要求不高的业务,其实完全可以通过本地缓存将表数据缓存到本地内存中,然后通过定时机制拉取表更新数据 直接从内存中获取数据,将会使得查询性能得到巨大的提升,并且由于更改少…...
redis 使用
打开redis 前台启动 同路径下打开redis-server 出现窗口,即启动成功 此时关闭窗口,redis关闭; 不管有没有使用密码,或者使用了什么密码,都能连上 如果使用下文提到的redis cli增加密码,就只能使用你设置的…...
使用 Pake 一键打包网页为桌面应用 / 客户端
项目 项目:https://github.com/tw93/Pake/ 免费ICO图片:https://icon-icons.com/zh/ 设置环境 以下教程仅针对windows系统适用 请确保您的 Node.js 版本为 18 或更高版本 文档:https://v1.tauri.app/zh-cn/v1/guides/getting-started/prerequ…...
vue.js【常用UI组件库】
Element Plus组件库 Element Plus是基于Vue 3开发的优秀的PC端开源UI组件库,它是Element的升级版,对于习惯使用Element的人员来说,在学习Element Plus时,不用花费太多的时间。因为Vue 3不再支持IE 11,所以Element Plu…...
基于vue框架的的地铁站智慧管理系统的设计n09jb(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。
系统程序文件列表 项目功能:用户,上班打卡,下班打卡,人员管理,交接班,视频巡检,车辆巡检,车辆管理 开题报告内容 基于Vue框架的地铁站智慧管理系统的设计开题报告 一、研究背景与意义 随着城市化进程的加速,地铁站作为城市交通系统的重要组成部分&am…...
《南京师大学报(自然科学版)》
《南京师大学报(自然科学版)》刊载内容主要包括:数学;物理学;化学;地理学;海洋科学;生物学;生态学;力学;电子科学与技术;计算机科学与…...
考研读研生存指南,注意事项
本视频课程,涉及考研读研的方方面面,从考研初试→复试面试→研究生生活→导师相处→论文专利写作混毕业,应有尽有。有了他,你的研究生生涯稳了。 读研考研注意事项,研究生生存指南。_哔哩哔哩_bilibili 一、考研初试注…...
爬虫结合项目实战
由于本人是大数据专业,所以准备的是使用pycharm工具进行爬虫爬取数据,然后实现一个可视化大屏 参考项目: 1.医院大数据可视化最后展示 2. 大数据分析可视化系统展示 代码包:...
【Next.js 项目实战系列】07-分配 Issue 给用户
原文链接 CSDN 的排版/样式可能有问题,去我的博客查看原文系列吧,觉得有用的话,给我的库点个star,关注一下吧 上一篇【Next.js 项目实战系列】06-身份验证 分配 Issue 给用户 本节代码链接 Select Button # /app/issues/[i…...
大型活动交通拥堵治理的视觉算法应用
大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动(如演唱会、马拉松赛事、高考中考等)期间,城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例,暖城商圈曾因观众集中离场导致周边…...
CentOS下的分布式内存计算Spark环境部署
一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架,相比 MapReduce 具有以下核心优势: 内存计算:数据可常驻内存,迭代计算性能提升 10-100 倍(文档段落:3-79…...
屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!
5月28日,中天合创屋面分布式光伏发电项目顺利并网发电,该项目位于内蒙古自治区鄂尔多斯市乌审旗,项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站,总装机容量为9.96MWp。 项目投运后,每年可节约标煤3670…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...
PAN/FPN
import torch import torch.nn as nn import torch.nn.functional as F import mathclass LowResQueryHighResKVAttention(nn.Module):"""方案 1: 低分辨率特征 (Query) 查询高分辨率特征 (Key, Value).输出分辨率与低分辨率输入相同。"""def __…...
JavaScript基础-API 和 Web API
在学习JavaScript的过程中,理解API(应用程序接口)和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能,使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...
Redis:现代应用开发的高效内存数据存储利器
一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发,其初衷是为了满足他自己的一个项目需求,即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源,Redis凭借其简单易用、…...
「全栈技术解析」推客小程序系统开发:从架构设计到裂变增长的完整解决方案
在移动互联网营销竞争白热化的当下,推客小程序系统凭借其裂变传播、精准营销等特性,成为企业抢占市场的利器。本文将深度解析推客小程序系统开发的核心技术与实现路径,助力开发者打造具有市场竞争力的营销工具。 一、系统核心功能架构&…...
springboot 日志类切面,接口成功记录日志,失败不记录
springboot 日志类切面,接口成功记录日志,失败不记录 自定义一个注解方法 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/***…...
深度剖析 DeepSeek 开源模型部署与应用:策略、权衡与未来走向
在人工智能技术呈指数级发展的当下,大模型已然成为推动各行业变革的核心驱动力。DeepSeek 开源模型以其卓越的性能和灵活的开源特性,吸引了众多企业与开发者的目光。如何高效且合理地部署与运用 DeepSeek 模型,成为释放其巨大潜力的关键所在&…...
