The original title “How to Decouple Code with Observer Pattern?” “, created on 2019-05-21
background
After the order payment is successful, the payment channel will call us back to inform us that the order has been paid. At this point, we need to do a series of operations:
- Modify order payment status;
- Send notification to merchant;
- Join queue for push order;
- other
We found that these are actually a series of operations that occur when the order has been paid. “_ one-to-many dependencies, where all dependent objects are notified and automatically updated when an object’s state changes” _ is a good fit for decoupling these business logic using “event-listener”. Many frameworks implement support for this pattern, but if not, it can be implemented manually to decouple different operations, weaken dependencies, and specifically address procedural noodle code issues. In addition, this may not be a problem if you use the current framework directly. This case can also be used to study the decoupling scheme.
implementation
The class diagram
steps
Create the event base class
namespace sdk\events\base;
use sdk\listeners\base\BaseListener;
abstract class BaseEvent
{
/** * Listeners to subscribe events *@var array
*/
protected $listens = [];
public function __construct()
{
$this->dispatch();
}
/** * distribution event */
protected function dispatch()
{
if (is_array($this->listens) && count($this->listens)) {
foreach ($this->listens as $item) {
$listener = new $item(a);if ($listener instanceof BaseListener) {
$result = $listener->handle($this); }}}}/** * Manually register a listener to the event *@param $listener
* @return null
*/
public function push($listener)
{
$result = null;
if ($listener instanceof BaseListener) {
$result = $listener->handle($this);
}
return $result; }}Copy the code
Create a listener base class
namespace sdk\listeners\base;
abstract class BaseListener
{
abstract public function handle($event);
}
Copy the code
Create the order Paid event class
namespace sdk\events;
use sdk\events\base\BaseEvent;
use sdk\listeners\NotifyAgentListener;
class OrderPaidEvent extends BaseEvent
{
/ * * *@varString Paid order number */
public $order_id;
/ * * *@varArray * Automatically registers listeners, note the order */
protected $listens = [
MarkOrderPaidListener::class,
NotifyAgentListener::class,
];
public function __construct(string $order_id.$promotion_amount)
{
$this->order_id = $order_id;
parent::__construct(); }}Copy the code
Add listener logic
Modify order status
namespace sdk\listeners;
use sdk\events\OrderPaidEvent;
use sdk\listeners\base\BaseListener;
class MarkOrderPaidListener extends BaseListener
{
/ * * *@param OrderPaidEvent $event
* @return bool
*/
public function handle($event)
{
// Modify the database code}}Copy the code
Notify agent
namespace sdk\listeners;
use sdk\events\OrderPaidEvent;
use sdk\listeners\base\BaseListener;
class NotifyAgentListener extends BaseListener
{
/ * * *@param OrderPaidEvent $event
* @return bool
*/
public function handle($event)
{
/ /... Specific processing logic}}Copy the code
More can be added later
use
The specific usage is as follows
use sdk\channel\yeebao\Pay as YeeBaoPay;
use sdk\events\OrderPaidEvent;
use sdk\listeners\PushYeebaoPaidOrderQueue;
use sdk\services\OrderService;
use sdk\tools\Func;
Class route_pay_notify_yb */
class route_callback_yb extends BaseApi
{
private $pay_type;
private $yeebaoPay;
public function __construct()
{
$this->yeebaoPay = new YeeBaoPay();
}
public function post()
{
try {
$this->handle();
} catch (Exception $exception) {
$log = $exception->getMessage() . ': file:' . $exception->getTraceAsString();
Func::wrtLog('An exception has occurred'.$log);
die($exception->getMessage()); }}private function handle()
{
$success_info = $this->getCallBackData();
// Here's the key point: instantiating this event automatically triggers the BaseEvent dispatch method
Listeners registered with the OrderPaidEvent class property $listens are executed sequentially, and the handle method performs the logical operation
// A single line of logic can trigger several listeners. The listener's processing logic is maintained independently, decoupling the code
$event = new OrderPaidEvent($success_info['order_id']);
// If there is a logic, you can register a listener manually after judgment
$event->push(new OrderSomeHandleListener());
/ /... The actual business logic judgment is more, omitted here
// Assembles the returned data upstream
}
private function getCallBackData() :array
{
// Receive data processing
return $success_info; }}Copy the code
conclusion
Using object-oriented technology, this dependency can be weakened
The follow-up to optimize
The downside of registered listener sequential execution is that it can block subsequent execution if there is a jam or an error. In this case, the listener’s processing logic can be queued and executed asynchronously. The specific practices
/ / BaseEvent
/** * $var bool * Add parameter control: default synchronization */
protected $sync = true;
protected $link = 'default';
protected function dispatch()
{
if (is_array($this->listens) && count($this->listens)) {
foreach ($this->listens as $item) {
$listener = new $item(a);if ($listener instanceof BaseListener) {
if($listener->sync === true) {
$listener->handle($this);
} else {
// Add queue logic, serialize $this and $listener into the queue. Queue drivers and links can be configured
}
}
}
}
}
Copy the code
Message-oriented middleware implementation considerations
inspiration
Laravel was used more before, the whole design can refer to Lavavel- event
extension
- Design patterns
- Observer model