本文最后更新于:24 天前
pipeline设计模式
背景
产品经理提了一个需求,完成任务赠送积分,如果遇到退款需要回收积分,任务是大概是这样的:
- 每天首次加入购物车赠送 10 积分
- 每天首单可以赠送 100 积分
- 购物累积金额达到 99 元赠送 100 积分
- 购物次数满 10 次赠送 100 积分
- 每日签到送 10 积分
- 还有很多奇奇怪怪的任务…
实现过程
if else
实现
简单, 二期可维护性降低
1 2 3 4 5
| if ("当天首单") { if ("累积99元") { if ("买满10次") {
|
###「简单工厂」+ 「策略模式」
管道模式
管道模式也称为流水线模式,英文:Pipeline。
Laravel 通过 Pipeline 实现 Middleware
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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
重写了部分方法。
- send() 需要传递的数据。
- through() 需要处理的任务
- via() 调用的方法名,默认为 handel()
- then() 对于返回数据的处理
编码
整体构建目录
1 2 3 4 5 6 7
| ├── PointTask │ ├── OverRmb.php // 满 N 元任务 │ ├── SignIn.php // 签到任务 │ ├── TodayFirst.php // 每日首单任务 │ ├── │ ├── PointTask.php // abstract 约束 │ └── PointTaskService.php // 对外调用方法
|
抽象公用方法,统一继承实现 发放积分 回收积分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <?php
namespace App\Lib\PointTask;
use Closure;
abstract class PointTask {
abstract function send($orderInfo, Closure $next);
public function recycle($orderInfo, Closure $next) { return $next($orderInfo); } }
|
因为有些任务是只有赠送,没有回收的情况,所以定义了 abstract
抽象方法,而不是 interface
,这样在具体任务的实现时可以不去实现 recycle
方法。
每日首单任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <?php
namespace App\Lib\PointTask;
use App\Services\Order\OrderServices; use App\Services\Users\UserServices; use Illuminate\Support\Facades\Log;
class TodayFirst extends PointTask {
function send($orderInfo, $next) { if (!app(OrderServices::class)->isTodayFirst($orderInfo['id'])) { return $next($orderInfo); } app(UserServices::class)->sendPoint(100); Log::info("首日第一次下单送积分++");
return $next($orderInfo); }
function recycle($orderInfo, $next) { $next($orderInfo); } }
|
买满多少钱赠送积分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <?php
namespace App\Lib\PointTask;
use Illuminate\Support\Facades\Log;
class OverRmb extends PointTask {
function send($orderInfo, $next) { if ($orderInfo['price'] < 100) { return $next($orderInfo); }
Log::info("满送积分++");
return $next($orderInfo);
}
function recycle($orderInfo, $next) { $next($orderInfo); } }
|
每日签到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <?php
namespace App\Lib\PointTask;
use App\Services\Users\UserServices; use Illuminate\Support\Facades\Log;
class SingIn extends PointTask {
function send($orderInfo, $next) { if (app(UserServices::class)->todayIsSinIn()) { $next($orderInfo); } app(UserServices::class)->sendPoint(10); Log::info("签到送积分++");
$next($orderInfo); }
function recycle($orderInfo, $next) { $next($orderInfo); } }
|
案例已经完成了方法的抽象,实现了 3 个具体积分任务,接下来编写 PointTaskService
实现 Pipeline
的组织
PointTaskService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| <?php
namespace App\Lib\PointTask;
use Illuminate\Routing\Pipeline; use App\Services\Order\OrderServices;
class PointTaskService { public $shopping = [OverRmb::class, TodayFirst::class];
public function shoppingSend($orderId) { $orderInfo = app(OrderServices::class)->getOrderInfoByOrderId($orderId); return (new Pipeline(app())) ->send($orderInfo) ->via('send') ->through($this->shopping) ->thenReturn(); }
public function shoppingRecycle($orderId) {
$orderInfo = app(OrderServices::class)->getOrderInfoByOrderId($orderId); return (new Pipeline(app())) ->send($orderInfo) ->via('recycle') ->through($this->shopping) ->thenReturn(); }
public function signIn() { return (new Pipeline(app())) ->via('send') ->through(SingIn::class) ->thenReturn(); }
}
|
thenReturn ()
方法
thenReturn()
方法是对 Pipleline
契约接口的 then()
方法的包装,默认的返回值是调用 send()
时传入的参数,如果对返回值需要再进行处理,则可调用 then()
, 传入一个匿名函数进行处理。
测试调用
1 2 3 4 5
| app(PointTaskService::class)->shoppingSend(12);
app(PointTaskService::class)->signIn();
|
如有新任务,则新建一个任务类继承 PointTask
实现 send
方法,如有可能收回积分则再实现 recycle
方法。
再在 PointTaskService
对外开放的 Service
中加入到指定位置,即可完成,不会影响到其他的业务逻辑。
已有的调用处也不用变动代码。
参考