工厂模式在平常业务代码中出现频率很高,仅次于单例模式了。一般在编写的代码的时候, 需要多个模块集中提供相同一种功能的服务 ,比如:短信发送(对接多个短信服务商)、支付(对接多个支付服务商)等业务时,我们通常会做一些代码设计思考:
-
不想因为对接多个模块时把代码搞得乱糟糟的,像个面糊糊一样糊在一起
-
不想写太多重复代码
-
不想处理依赖搞的乱七八糟
-
不想后续又对接了一个新的服务商而动现有跑的良好的代码等等
这个时候就可以思考是不是有已经有的设计模式可以使用,那么首先想到的就是 工厂模式,它可以解决以下几种情况:
- 你可以避免创建者和具体产品之间的紧密耦合。
- 单一职责:你可以将产品创建代码放在程序的单一位置, 从而使得代码更容易维护。
- 开闭原则:无需更改现有客户端代码, 你就可以在程序中引入新的产品类型。
但是它也有缺点,其中一个就是: 应用工厂方法模式需要引入许多新的子类, 代码可能会因此变得更复杂。 在现实中的例子比如短信发送这个功能,项目一开始只对接了七牛云和阿里云两个短信服务商,但是短信发送这一块算你们业务逻辑中比较重要的一环,所以你的boss希望多增加几个服务商来做备用,那么随着对接的服务商越来越多,你的工厂就越来越大,同时调用不同的服务商工厂部分的代码也可能充满了大量的if else 或switch 这样的判断,那么这个缺点可能会违背我们当初对代码结构设计的初衷了,当然后续会聊到其他设计模式去解决这个问题,当前我们先只讨论工厂模式的思想和实现。
目前很多博客或github上都有关于工厂模式的实例代码,但是没有几个能贴近现实业务,当然思想就是这么个思想,大家理解了思想那么example中不管是“动物厂”还是“手机厂”其实无所谓。下面的实例代码我选择了比较贴合web开发中的一个场景:手机信息发送来做example,希望能更好的帮助大家理解工厂模式。
工厂模式的思想很多博客有更好、更深入的讲解,这里不在班门弄斧。大家可以相关搜索引擎上搜索,这里直接展示代码。
<?php
/**
* 工厂模式案例,以短信发送类作为案例
* 案例中,假设使用阿里云和七牛云2个短信服务商
*
*
* |-------------| |-------------|
* | Factory | ----> | Product |
* |-------------| |-------------|
* | |
* |---------| |----------------|
* |----------| |----------| |----------| |----------|
* | AFactory | | BFactory | | AProduct | | BProduct |
* |----------| |----------| |----------| |----------|
* | |
* return AProduct |
* return BProduct
*
* 为了方便演示,将所有代码写在同一个文件中,实际项目中不推荐次做法
* 推荐目录结构为:
* |-Sms
* |--SmsFactory.php
* |--SmsDriver.php
* |--Factory
* |----AliyunFactory.php
* |----QiniyunFactory.php
* |--SmsDriver
* |----AliyunDriver.php
* |----QiniuyunDriver.php
*/
abstract class SmsFactory {
abstract public function driver() : SmsDriver;
/**
* 发送短信
* @return mixed
*/
public function sendSms() {
return ($this->driver())->send();
}
/**
* 获取当前短信驱动
* @return mixed
*/
public function getCurrentDriver() {
return ($this->driver())->getDriver();
}
}
/**
* 阿里云短信工厂
* Class AliyunFactory
*/
class AliyunFactory extends SmsFactory {
private $phone, $content, $templateCode;
public function __construct(string $phone,string $content,string $templateCode)
{
$this->phone = $phone;
$this->content = $content;
$this->templateCode = $templateCode;
}
public function driver(): SmsDriver{
return new AliyunDriver($this->phone,$this->content,$this->templateCode);
}
// 做一些其他的 阿里云短信准备
// 比如检查配置、检查可用性、依赖处理
}
/**
* 七牛云短信工厂
* Class QiniuyunFactory
*/
class QiniuyunFactory extends SmsFactory {
private $phone, $content, $templateCode;
public function __construct(string $phone,string $content,string $templateCode)
{
$this->phone = $phone;
$this->content = $content;
$this->templateCode = $templateCode;
}
public function driver(): SmsDriver{
return new QiniuyunDriver($this->phone,$this->content,$this->templateCode);
}
// 做一些其他的 七牛云短信准备
// 比如检查配置、检查可用性、依赖处理
}
interface SmsDriver {
public function send();
public function getDriver();
}
/**
* 阿里云短信驱动
* Class AliyunDriver
*/
class AliyunDriver implements SmsDriver {
/** @var string 短信模板 */
private $templateCode = '';
/** @var string 发送的手机号 */
private $currentPhone = '';
/** @var string 发送的信息内容 */
private $content = '';
public function __construct(string $phone,string $content, string $templateCode = '')
{
$this->templateCode = $templateCode;
$this->currentPhone = $phone;
$this->content = $content;
}
public function send() {
// 实际组装请求数据,
// 向服务网关发送数据请求
// 处理返回、报错
}
/**
* 对接发送接口
*/
private function curl() {}
public function getDriver()
{
return __CLASS__;
}
}
/**
* 七牛云短信驱动
* Class QiniuyunDriver
*/
class QiniuyunDriver implements SmsDriver {
/** @var string 短信模板 */
private $templateCode = '';
/** @var string 发送的手机号 */
private $currentPhone = '';
/** @var string 发送的信息内容 */
private $content = '';
public function __construct(string $phone,string $content, string $templateCode = '')
{
$this->templateCode = $templateCode;
$this->currentPhone = $phone;
$this->content = $content;
}
public function send() {
// 实际组装请求数据,
// 向服务网关发送数据请求
// 处理返回、报错
}
public function getDriver()
{
return __CLASS__;
}
}
/**
* 验证码类
* Class SendCaptcha
*/
class SendCaptcha {
public $phone;
public function __construct(string $phone)
{
$this->phone = $phone;
}
public function send(SmsFactory $factory) {
// 创建验证码
$captcha = $this->createCaptcha();
// 存储验证码
$cacheKey = 'cacha_captcha_'.$this->phone;
// 选择短信的驱动类
// 实例使用阿里云
$aliyunTemplateCode = '32123123';
$res = $this->run(new AliyunFactory($this->phone,$captcha,$aliyunTemplateCode));
if (!$res) {
return false;
}
$this->cache($cacheKey,$captcha);
return true;
}
/**
* 缓存类
* @param string $key
* @param string $value
* @return bool
*/
private function cache(string $key, string $value) {return true;}
private function run(SmsFactory $factory) {
return $factory->sendSms();
}
private function createCaptcha() {
return mt_rand(111111,999999);
}
}