background

The background is that the product manager has put forward a demand, and the completion of the task will give away points. If you need to recover the points if you encounter a refund, the task is roughly like this:

  1. 10 points for the first time you add to your cart every day
  2. You can get 100 points for the first order every day
  3. Receive 100 points when the accumulated purchase amount reaches 99 yuan
  4. 100 points will be awarded for 10 purchases
  5. Daily check-in to get 10 points
  6. There are still many strange tasks...

Implementation process

analyze

When adding points to the shopping cart, the task is over. When purchasing a product, it is possible to hit multiple conditions at the same time and give points at the same time. After all the conditions are hit, the task is over.

After analyzing the requirements, the next step is to think about how to implement it. The easiest way is to implement if else:

// Successful payment triggers bonus points
if ("First order of the day") { // Reward shopping points }
if ("accumulate 99 yuan") { // Reward shopping points }
if ("Buy 10 times") { // Reward shopping points }
// ...

When the demand was raised, the product had already thought of the second-phase integral task requirements, so as the number of tasks increased, the maintainability would definitely decrease, so the idea of ​​using if else was immediately rejected.

Then I thought of the experience of "Simple Factory" + "Strategy Mode" that I used in Payment before. There should be a design pattern that meets the requirements to solve this kind of problem. . Because the overall process is a straight-line process, which is executed in sequence, the chain of responsibility model comes to mind. By querying the relevant information, it seems that the "pipeline pattern", a variant of the chain of responsibility pattern, is more suitable for this application.

Pipe mode

Pipeline mode is also called pipeline mode, English: Pipeline.

The word Pipeline is very familiar. I seem to have seen it there. After thinking about it, I have seen it in Laravel. Before analyzing [Laravel Dependency Injection and Inversion of Control](https://learnku.com/articles/ 56111) when I saw it.

Laravel implements Middleware via Pipeline: [https://github.com/laravel/framework/blob/9.x/src/Illuminate/Foundation/Http/Kernel.php#L131](https://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());
}

Continuing to follow the implementation of Pipeline, I found that Laravel implements a Pipeline contract interface, and implements two pipelines, a public Pipeline and a Routing-related Pipeline, in which the Routing Pipleline inherits the public Pipeline and rewrites the part. method.

Pipleline contract interface

image.png

  • send() data to be passed.
  • through() tasks to be processed
  • The name of the method called by via(), defaults to handel()
  • then() for the processing of the returned data

Seeing this, since Laravel has implemented the public method of Pipleline, it can be used directly. If you wanted to implement it at the beginning, you would have to work overtime, but now you don't need it. so elegant~

encoding

Overall build directory

├── PointTask
│ ├── OverRmb.php // Full N-yuan tasks
│ ├── SignIn.php // Sign in task
│ ├── TodayFirst.php // Daily first task
│ ├──
│ ├── PointTask.php // abstract constraint
│ └── PointTaskService.php // external call method

Since it is necessary to take into account future modifications and generality, it is necessary to abstract public methods and implement unified inheritance.

After analysis, there are two main methods: sending points and recovering points, so first abstract these two methods.

abstract class PointTask
{
    // send points
    abstract function send($next, $orderInfo);

    // recover points
    public function recycle($next, $orderInfo)
    {
        return $next($orderInfo);
    }
}

Because some tasks are only given and not recycled, the abstract abstract method is defined instead of interface, so that the recycle method can not be implemented in the implementation of specific tasks.

  • Daily first order task

    class TodayFirst extends PointTask
    {
        function send($next, $orderInfo) {
            // There is an order to directly execute the next task
            if (!app(PayOrderService::class)->isTodayFirst($orderInfo['orderSn'])) {
                return $next($orderInfo);
            }
            // give away points
            app(PayOrderService::class)->sendPoint(100);
            return $next($orderInfo);
        }
    
        function recycle($next, $orderInfo) {
            // Recover points, code...
            $next($orderInfo);
        }
    }
  • Free points for how much you buy

    class OverRmb extends PointTask
    {
        function send($next, $orderInfo) {
            // If less than 100 yuan, execute the next task directly
            if ($orderInfo['price'] < 100) {
                return $next($orderInfo);
            }
    
            // gift points, code...
            return $next($orderInfo);
        }
    
        function recycle($next, $orderInfo) {
            // Recover points, code...
            $next($orderInfo);
        }
    }
  • Daily check-in

    class SignIn extends PointTask
    {
        function send($next, $orderInfo)
        {
            // Signed in and execute the next task directly
            if (app(UserService::class)->todayIsSinIn()) {
                return $next($orderInfo);
            }
    
            // gift points, code...
            app(PayOrderService::class)->sendPoint(10);
            return $next($orderInfo);
        }
    }

The case has completed the abstraction of methods and implemented three specific integral tasks. Next, write PointTaskService to realize the organization of Pipeline. For those who do not understand the Pipeline provided by Laravel, you can refer to the reference article below.

PointTaskService

class PointTaskService
{
    // define tasks that may be triggered at the same time
    public $shopping = [TodayFirst::class, OverRmb::class];

    // shopping bonus points
    public function shoppingSend($orderSn) {
        $orderInfo = app(PayOrderService::class)->getOrderInfoByOrderNo($orderSn);
        return (new Pipeline(app()))
            ->send($orderInfo)
            ->via('send')
            ->through($this->shopping)
            ->thenReturn();
    }

    // shopping refund recovery points
    public function shoppingRecycle($orderSn) {
        $orderInfo = app(PayOrderService::class)->getOrderInfoByOrderNo($orderSn);
        return (new Pipeline(app()))
            ->send($orderInfo)
            ->via('recycle')
            ->through($this->shopping)
            ->thenReturn();
    }

    // check in daily
    public function signIn() {
        return (new Pipeline(app()))
            ->via('send')
            ->through(SignIn::class)
            ->thenReturn();
    }
}

thenReturn() method

The thenReturn() method is a wrapper for the then() method of the Pipeline contract interface. The default return value is the parameter passed in when calling send(). If the return value needs to be processed again, it can be called then(), pass in an anonymous function for processing.

Call after successful payment:

if ($isPaid) {
   // The actual effect of gift points can not be so timely, but can be pushed to the queue for asynchronous execution.
   app(PointTaskService::class)->shoppingSend("0722621373");
}

After the refund is successful, call:

if ($isRefund) {
   app(PointTaskService::class)->shoppingRecycle("0722621373");
}

Daily check-in call:

if ($signIn) {
   app(PointTaskService::class)->signIn();
}

The file seems to be quite a lot, but the order is relatively clear:

  1. If there is a new task, create a new task class that inherits PointTask and implements the send method. If it is possible to recover the points, then implement the recycle method.
  2. Then add it to the specified location in the Service open to the outside world in PointTaskService, and you can complete it without affecting other business logic.
  3. There is no need to change the code at the existing call site.

Summarize

  1. The source code that has been carefully analyzed may be forgotten, but it can be recalled at the right time to prove that it was an effective analysis reading at that time.
  2. For small needs of sewing and repairing, when you encounter bad code, you basically continue to pile up the code, but if you have the opportunity to take over the complete function point, then write it as well as possible.
Likes(0)

Comment list count 0 Comments

No Comments