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
中,便于管理。若有修改需求,例如调整具体厂商的实现逻辑,我们只需修改对应的实现类,而不必在各处重复更改。
通过这种设计,工厂模式让短信发送逻辑更加灵活、可扩展且便于维护,为系统增加新的厂商支持变得轻松简单。这不仅提升了代码质量,也提高了系统的稳定性。
最后
通过工厂模式,我们有效地将对象的创建过程与使用逻辑解耦,使系统的扩展性和维护性得到了显著提升。在多厂商短信发送的场景中,工厂模式使得不同厂商的短信服务可以通过统一的接口进行调用,简化了代码结构。同时,工厂模式还符合开闭原则,让我们能够在不修改已有代码的前提下,轻松地接入新的厂商支持。这样的设计不仅提高了代码的灵活性和可读性,也降低了系统的耦合度,使项目更易于维护和拓展。
工厂模式在实际开发中应用广泛,尤其适用于需要动态生成对象且对象种类较多的场景。通过合理运用工厂模式,我们可以更有效地应对业务需求的变化,让系统保持稳定、可扩展的同时具备更高的质量。