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:
- 10 points for the first time you add to your cart every day
- You can get 100 points for the first order every day
- Receive 100 points when the accumulated purchase amount reaches 99 yuan
- 100 points will be awarded for 10 purchases
- Daily check-in to get 10 points
- 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.
send()
data to be passed.through()
tasks to be processed- The name of the method called by
via()
, defaults tohandel()
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:
- If there is a new task, create a new task class that inherits
PointTask
and implements thesend
method. If it is possible to recover the points, then implement therecycle
method. - 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. - There is no need to change the code at the existing call site.
Summarize
- 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.
- 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.
Post comment 取消回复