相当多的时候,我们写业务代码时,为了尽快实现产品功能,并不会采用较为复杂的设计模式。这也是为什么很多老项目往往有一堆if-else,别怪前任,有时候他也是不得已而为之,业务一直变化+膨胀,Boss催得又紧,通常都会选择用最快速的方法来实现逻辑再说。以最常见的接入第三方支付为例,接入微信支付、支付宝支付、苹果支付等等。
最简单粗暴的写法是,每次接入新的支付渠道都在PayController中引入新渠道的支付处理类,在switch中增加case来处理对应的渠道逻辑,代码如下:
<?php
namespace app\controller;
use app\model\Goods;
use app\model\Payment;
use payment\wxpay\WxPay;
use payment\alipay\AliPay;
class PayController extends Base;
{
function __construct()
{
// code...
}
// 以下均为模拟代码,很多逻辑没写出来,上面的类也是我虚构的,大体是Thinkphp为例
public function doPay()
{
$pay_way = $_POST['pay_way'];
$goods_id = $_POST['goods_id'];
$goods = Goods::get($goods_id);
$config = Payment::where(['pay_way' => $pay_way])->find();
switch ($pay_way) {
case 'wxpay':
$payment = new WxPay($config);
break;
case 'alipay':
$payment = new AliPay($config);
break;
// 更多渠道...
default:
return self::error('暂不支持该渠道');
break;
}
// 实际情况以你自己的逻辑参数为准
$result = $payment->doPay([
'goods_id' => $goods_id,
'goods_name' => $goods->title,
'amount' => $goods->price,
'order_no' => makeOrderNo(),
]);
return self::success($result);
}
}
随着对接的支付渠道越来越多,我们发现use的类越来越多,case的内容也越来越多,产生了大量重复的代码,这本能地让我感到恶心,于是我决定在config中配置好由那个支付类处理,然后通过反射类来实现:
<?php
namespace app\controller;
use app\model\Goods;
use app\model\Payment;
class PayController extends Base;
{
function __construct()
{
// code...
}
// 以下均为模拟代码,很多逻辑没写出来,上面的类也是我虚构的,大体是Thinkphp为例
public function doPay()
{
$pay_way = $_POST['pay_way'];
$goods_id = $_POST['goods_id'];
$goods = Goods::get($goods_id);
$config = Payment::where(['pay_way' => $pay_way])->find();
// 如payment\wxpay\WxPay
if ($config && class_exists($config->class_name)) {
$reflection = new \ReflectionClass($config->class_name);
$payment = $reflection->newInstanceArgs($config->class_name);
}else{
return self::error('暂不支持该渠道');
}
// 实际情况以你自己的逻辑参数为准
$result = $payment->doPay([
'goods_id' => $goods_id,
'goods_name' => $goods->title,
'amount' => $goods->price,
'order_no' => makeOrderNo(),
]);
return self::success($result);
}
}
此时大部分情况其实已经满足了......
直到我开始接入各种短信渠道,我又用了一次反射,从某种意义上来说,我又重复了一堆代码,于是我终于开始考虑依赖注入的方式,首先实现一个用来自动实例化以及存放实例的DI容器类:
<?php
namespace app\common;
class DI
{
private $container = array();
// 向容器内注入实例
public function set($key, $class, $arg = [])
{
if (count($arg) == 1 && is_array($arg[0])) {
$arg = $arg[0];
}
/*
* 注入的时候不做任何的类型检测与转换
* 由于编程人员人为问题,该注入资源并不一定会被用到
*/
$this->container[$key] = array(
"class" => $class,
"params" => $arg,
);
}
// 移除容器内的实例
public function delete($key)
{
unset($this->container[$key]);
}
// 清空容器
public function clear()
{
$this->container = array();
}
// 获取容器指定实例
public function get($key)
{
if (isset($this->container[$key])) {
$result = $this->container[$key];
if (is_object($result['class'])) {
return $result['class'];
} else if (is_callable($result['class'])) {
return $this->container[$key]['class'];
} else if (is_string($result['class']) && class_exists($result['class'])) {
$reflection = new \ReflectionClass($result['class']);
$ins = $reflection->newInstanceArgs($result['params']);
$this->container[$key]['class'] = $ins;
return $this->container[$key]['class'];
} else {
return $result['class'];
}
} else {
return null;
}
}
}
控制器就变成如下代码:
<?php
namespace app\controller;
use app\common\DI;
use app\model\Goods;
use app\model\Payment;
use app\services\PaymentService
class PayController extends Base;
{
function __construct()
{
// code...
}
// 以下均为模拟代码,很多逻辑没写出来,上面的类也是我虚构的,大体是Thinkphp为例
public function doPay()
{
$pay_way = $_POST['pay_way'];
$goods_id = $_POST['goods_id'];
$goods = Goods::get($goods_id);
$config = Payment::where(['pay_way' => $pay_way])->find();
// 如payment\wxpay\WxPay
$di = new DI();
$di->set($pay_type, $config->class_name);
$payment = new PaymentService($di->get($pay_type));
$payment->config($config);
// 实际情况以你自己的逻辑参数为准
$result = $payment->doPay([
'goods_id' => $goods_id,
'goods_name' => $goods->title,
'amount' => $goods->price,
'order_no' => makeOrderNo(),
]);
return self::success($result);
}
}
PaymentService代码如下:
<?php
namespace app\services;
use app\model\Payment;
class PaymentService
{
private $pay;
public function __construct($pay)
{
$this->pay = $pay;
}
public function config(Payment $config)
{
$this->pay->config($config);
}
public function doPay($data)
{
return $this->pay->doPay($data);
}
public function notify($data)
{
return $this->pay->notify($data);
}
}
以上就是多种类型的不同渠道(支付、短信、第三方登录等)对接的大致流程,具体的支付类没有放出代码,实现起来比较简单,就不贴代码了。