日怪了,支付就支付呗,干嘛跟工厂模式扯在一起?且听我慢慢道来。
当今中国支付市场,可谓百花齐放。搞得开发人员痛苦不堪。来,咋们说说常用的支付就有:支付宝、微信、银联、百度钱包、Apple Pay、京东支付等等。有这么多,可苦了我们这些程序开发者了。各个支付接口之前参数不同,调用流程也不同。
接口太多的例子
下面的例子,我仅仅使用支付宝、微信进行说明。只是为了便于大家理解相关概念。
class 支付宝
{
public function 即时到账($data)
{
// 即时到账接口
}
public function 移动支付接口($data)
{
// 移动支付接口
}
}
class 微信支付
{
public function APP支付($data)
{
}
public function JS支付($data)
{
}
public function 扫码支付($data)
{
}
}
假设上面是我们写好的lib。这种lib的提供简单暴力,仅考虑实现功能,对于易用性,易维护性均没有考虑。一般来说我们的应用,可能都需要支持网站上的支付、app上的支付。那么作为客户端,可能要许这样用(这里客户端是指:调用者)
$alipay = new 支付宝();
// 构造网站支付的数据
$pcData = [];
$alipay->即时到账($pcData);
// 构造移动支付数据
$mobileData = [];
$alipay->移动支付($mobileData);
// 微信支付,再来一次
$wxpay = new 微信支付();
// 网站支付的数据
$wxpcData = [];
$wxpay->扫码支付($wxpcData);
// app支付
$appData = [];
$wxpay->APP支付($appData);
如果做过支付集成的同学,应该很清楚上面的过程。这里还有很多细节都一一放弃了,比如:不同接口签名方式不同、不同接口回调也不同,需要分别处理。蛋碎不蛋碎?
分析问题
如果仅仅从用的角度来说,上面的lib包也能够满足业务,因为支付这个东西,一般来说写好后,很好有人去动它。但是,现在的支付产品本身也在不断改进体验,可能一段时间后参数变了?一段时间后有新的支付方式出现?那么上面的代码,我们该怎么处理?
没错,只能修改代码,这就违背了 [开闭原则] 。而且一不小心,还可能把以前对的给改错了。
解决问题
要解决上面的问题,我采用这个方法:接口规范、工厂生产。
其实对于客户端,我并不关心具体的支付接口,对于理想的调用方式
$pay = PayFactory::getInstance(支付方式);
// 支付数据
$data = [];
$pay->charge($data);
对,就是上面这样就搞定了。我只是告诉工厂我想要一个什么支付的东西,然后给一个统一的数据格式,你帮我处理。我不再关心每个具体的接口参数名、不再关心具体的实例话过程。
我们来尝试用 简单工厂解决上面的问题
// 支付的数据,用于规范客户端的数据
class PayData
{
public $orderNo;
public $totalFee;
public $subject;
public $body;
public $timeExpire;
}
class ChannelEnum
{
const CHANNEL_IS_即时到账 = 'xxx';
const CHANNEL_IS_移动支付 = 'xxx';
// ... ...
}
interface ChargeInterface
{
// 支付接口
public function charge(PayData $data);
}
class 即时到账 implements ChargeInterface
{
public function charge(PayData $data)
{
// 处理为数据
// 发起支付
}
}
class 移动支付 implements ChargeInterface
{
public function charge(PayData $data)
{
// 处理为数据
// 发起支付
}
}
// ... ...
剩下的接口,就不再进行写了。大同小异,那么还差一个。客户端怎么进行实力化呢?难道还是用new?如果是这样,今晚跑题就太严重了。虽然现在我说的东西也有点超出范围了。哈哈
// 用一个静态工厂来解决客户端实例化的问题
class PayFactory
{
public static function getInstance($channel)
{
$instance = null;
swich ($channel) {
case ChannelEnum::CHANNEL_IS_即时到账:
$instance = new 即时到账();
break;
case ChannelEnum::CHANNEL_IS_移动支付:
$instance = new 移动支付();
break;
// 其他实例化
}
return $instance;
}
}
经过这些步骤,从某种程度上已经解决了 开闭 的问题。比如:现在我新增一个支付接口,添加的是一个类,然后在静态工厂中写一个switch分支。大大降低了风险。
另外客户端的调用复杂度也下降太多。所有的数据处理与签名,都放到具体的支付接口实现类中了。
但是细心的话,大家会发现,其实每次有修改,虽然对支付接口部分实现了扩展,关闭了修改。但是静态工厂类一旦有新的支付加入,还是会 违背 [开闭原则]
终于到主题了
哎,说了这么多废话,终于引出今晚的主题了。上面的方式叫做静态工厂模式,严格来说它不算一种设计模式。从他说起,是因为我们常常会用到它。而且也容易理解。
上面我们说了它违背了 开闭原则 ,那么这部代码我们怎么优化?这里就来了工厂类。用工厂类改写上面的静态工厂类,请注意区别哈。
abstract class Factory
{
abstract public function getInstance();
}
class 立即到账Factory
{
public function getInstance()
{
return new 立即到账();
}
}
class 移动支付Factory
{
public function getInstance()
{
return new 移动支付();
}
}
// 其他工厂
看到了吧,对应每一个支付,就有一个工厂与之对应。如果有新的支付加入进来,新增加一个对应的工厂即可。
工厂类产生的新问题
虽然上面的代码解决了开闭原则。但是其实又给客户端的调用带来了新的复杂问题。用静态工厂的时候直接给一个参数就可以拿到对应的支付对象,现在这个判断的任务落在了客户端。
当然上面的问题也不是不能解决。大家可以去了解一下反射。在我上一篇文章 php的设计模式:单例模式 就用到了部分反射的知识,大家可以参考然后来试试更简单的调用,减少客户端的复杂度。
除了反射,针对php还是利用脚本语言的优势。比如类名可以写成变量,来进行对象的创建(其本质还是用到了反射)
这就完了
本来还有个抽象工厂模式,这货说实话,真正用的很少,至少我目前很少有项目需求需要设计的这么复杂。大家已经看到了,工厂模式的时候,已经会产生大量的类了,所以有时候在进行设计模式的运用时,我还是建议大家进行一个权衡。
另外针对上面所说的,我自己集成了一个支付宝、微信支付的开源项目。大家如果有需要的可以免费拿去用,如果觉得还不错的,可以打赏我的呃。
项目oschian地址 :支付宝、微信支付集成libary
项目github地址:支付宝、微信支付集成libary
这个项目发布一天,120个start。上了oschina的热门榜首。我自己所在的公司,也已经开始采用这个包。也欢迎大家提bug(当前并不是所有接口都经过严格测试,可能有坑,请小心)