支付时用到的「简单工厂」+ 「策略模式」经验,应该是有符合要求的设计模式能解决这类问题。因为整体流程是一条直线的流程,依次执行,就想到责任链模式。通过查询相关资料,责任链模式的变种「管道模式」似乎更适合应用至此。
Laravel 依赖注入和控制反转 时见到过。Laravel 通过 Pipeline
实现 Middleware
: github.com/laravel/framework/blob/...
use Illuminate\Routing\Pipeline; | |
protected function sendRequestThroughRouter($request) | |
{ | |
$this->app->instance('request', $request); | |
Facade::clearResolvedInstance('request'); | |
$this->bootstrap(); | |
return (new Pipeline($this->app)) | |
->send($request) | |
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) | |
->then($this->dispatchToRouter()); | |
} |
继续往上追 Pipeline
的实现,发现 Laravel 是实现了一个 Pipleline 契约接口,实现了两个管道分别是公用的Pipleline和一个 Routing 相关的 Pipleline,其中Routing Pipleline是继承了公用的 Pipleline 重写了部分方法。
Pipleline 契约接口
send()
需要传递的数据。through()
需要处理的任务via()
调用的方法名,默认为handel()
then()
对于返回数据的处理
看到这里,既然Laravel 已经实现了 Pipleline 的公用方法,那就可以直接拿来用了,刚开始还想着要实现的话还得加加班呢,现在不用了。真优雅~
编码
整体构建目录
├── PointTask | |
│ ├── OverRmb.php // 满 N 元任务 | |
│ ├── SignIn.php // 签到任务 | |
│ ├── TodayFirst.php // 每日首单任务 | |
│ ├── | |
│ ├── PointTask.php // abstract 约束 | |
│ └── PointTaskService.php // 对外调用方法 |
既然要考虑到以后的修改以及通用性,那就要抽象出公用方法,统一继承实现。
经过分析主要方法有两个:分别是发送积分和回收积分,所以先抽象这两个方法。
abstract class PointTask | |
{ | |
// 发送积分 | |
abstract function send($next, $orderInfo); | |
// 回收积分 | |
public function recycle($next, $orderInfo) | |
{ | |
return $next($orderInfo); | |
} | |
} |
因为有些任务是只有赠送,没有回收的情况,所以定义了 abstract
抽象方法,而不是 interface
,这样在具体任务的实现时可以不去实现 recycle
方法。
- 每日首单任务
class TodayFirst extends PointTask | |
{ | |
function send($next, $orderInfo) { | |
// 有订单直接执行下一个任务 | |
if (!app(PayOrderService::class)->isTodayFirst($orderInfo['orderSn'])) { | |
return $next($orderInfo); | |
} | |
// 赠送积分 | |
app(PayOrderService::class)->sendPoint(100); | |
return $next($orderInfo); | |
} | |
function recycle($next, $orderInfo) { | |
// 回收积分, code... | |
$next($orderInfo); | |
} | |
} |
- 买满多少钱赠送积分
class OverRmb extends PointTask | |
{ | |
function send($next, $orderInfo) { | |
// 小于 100 元直接执行下一个任务 | |
if ($orderInfo['price'] < 100) { | |
return $next($orderInfo); | |
} | |
// 赠送积分, code... | |
return $next($orderInfo); | |
} | |
function recycle($next, $orderInfo) { | |
// 回收积分, code... | |
$next($orderInfo); | |
} | |
} |
- 每日签到
class SignIn extends PointTask | |
{ | |
function send($next, $orderInfo) | |
{ | |
// 已签到直接执行下一个任务 | |
if (app(UserService::class)->todayIsSinIn()) { | |
return $next($orderInfo); | |
} | |
// 赠送积分, code... | |
app(PayOrderService::class)->sendPoint(10); | |
return $next($orderInfo); | |
} | |
} |
案例已经完成了方法的抽象,实现了 3 个具体积分任务,接下来编写 PointTaskService
实现 Pipeline
的组织。对 Laravel 提供的 Pipeline
不太明白的朋友,可以参考下方的参考文章。
PointTaskService
class PointTaskService | |
{ | |
// 定义了可能同时触发的任务 | |
public $shopping = [TodayFirst::class, OverRmb::class]; | |
// 购物赠送积分 | |
public function shoppingSend($orderSn) { | |
$orderInfo = app(PayOrderService::class)->getOrderInfoByOrderNo($orderSn); | |
return (new Pipeline(app())) | |
->send($orderInfo) | |
->via('send') | |
->through($this->shopping) | |
->thenReturn(); | |
} | |
// 购物退款回收积分 | |
public function shoppingRecycle($orderSn) { | |
$orderInfo = app(PayOrderService::class)->getOrderInfoByOrderNo($orderSn); | |
return (new Pipeline(app())) | |
->send($orderInfo) | |
->via('recycle') | |
->through($this->shopping) | |
->thenReturn(); | |
} | |
// 每日签到 | |
public function signIn() { | |
return (new Pipeline(app())) | |
->via('send') | |
->through(SignIn::class) | |
->thenReturn(); | |
} | |
} |
thenReturn() 方法
thenReturn()
方法是对Pipleline 契约接口的 then()
方法的包装,默认的返回值是调用 send()
时传入的参数,如果对返回值需要再进行处理,则可调用 then()
, 传入一个匿名函数进行处理。
支付成功后调用:
if ($isPaid) { | |
// 赠送积分实效可以不用那么及时,可推到队列异步执行。 | |
app(PointTaskService::class)->shoppingSend("0722621373"); | |
} |
退款成功后调用:
if ($isRefund) { | |
app(PointTaskService::class)->shoppingRecycle("0722621373"); | |
} |
每日签到调用:
if ($signIn) { | |
app(PointTaskService::class)->signIn(); | |
} |
文件看起来似乎挺多的,但条理还是比较清晰的:
- 如有新任务,则新建一个任务类继承
PointTask
实现send
方法,如有可能收回积分则再实现recycle
方法。 - 再在
PointTaskService
对外开放的 Service 中加入到指定位置,即可完成,不会影响到其他的业务逻辑。 - 已有的调用处也不用变动代码。
总结
- 认真分析过的源码可能会忘记,但能在合适的时间回想起来就证明当时是有效的分析阅读。
- 平时缝缝补补的小需求遇到糟心的代码基本也是往上继续堆代码,但如果有机会接手完整的功能点,那就尽可能的写好点吧。
源码
github.com/zxr615/rewrite-pay-modu...