背景

背景就是产品经理提了一个需求,完成任务赠送积分,如果遇到退款需要回收积分,任务是大概是这样的:

  1. 每天首次加入购物车赠送 10 积分
  2. 每天首单可以赠送 100 积分
  3. 购物累积金额达到 99 元赠送 100 积分
  4. 购物次数满 10 次赠送 100 积分
  5. 每日签到送 10 积分
  6. 还有很多奇奇怪怪的任务...

实现过程

分析

当加入购物车时赠送积分,任务结束,当购买商品时就有可能会同时命中多个条件同时赠送积分,命中的所有条件都赠送后,任务结束。

分析完需求,接下来就想如何实现,最简单的方法也就是 if else 实现:

// 支付成功触发赠送积分
if ("当天首单") { // Reward shopping points }
if ("累积99元") { // Reward shopping points }
if ("买满10次") { // Reward shopping points }
// ...

提需求的时候产品已经想到二期的积分任务需求了,所以随着任务的增多,可维护性一定会降低,所以立马否决了用if else 实现的想法

紧接着想到了之前做支付时用到的「简单工厂」+ 「策略模式」经验,应该是有符合要求的设计模式能解决这类问题。因为整体流程是一条直线的流程,依次执行,就想到责任链模式。通过查询相关资料,责任链模式的变种「管道模式」似乎更适合应用至此。

管道模式

管道模式也称为流水线模式,英文:Pipeline。

看到 Pipeline 这个单词非常熟悉,似乎在那里见过,思来想去,是在 Laravel 里面见过,之前分析 Laravel 依赖注入和控制反转 时见到过。

Laravel 通过 Pipeline 实现 Middlewarehttps://github.com/laravel/framework/blob/9.x/src/Illuminate/Foundation/Http/Kernel.php#L131

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 契约接口

image.png

  • 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();
}

文件看起来似乎挺多的,但条理还是比较清晰的:

  1. 如有新任务,则新建一个任务类继承 PointTask 实现 send 方法,如有可能收回积分则再实现 recycle 方法。
  2. 再在 PointTaskService 对外开放的 Service 中加入到指定位置,即可完成,不会影响到其他的业务逻辑。
  3. 已有的调用处也不用变动代码。

总结

  1. 认真分析过的源码可能会忘记,但能在合适的时间回想起来就证明当时是有效的分析阅读。
  2. 平时缝缝补补的小需求遇到糟心的代码基本也是往上继续堆代码,但如果有机会接手完整的功能点,那就尽可能的写好点吧。
点赞(0)

评论列表 共有 0 评论

暂无评论

微信服务号

微信客服

淘宝店铺

support@elephdev.com

发表
评论
Go
顶部