PHPer月工做總結之觀察者&裝飾器模式

前言

仍是每個月的目標至少寫一篇文章,一晃八月份就要過去了,這個月依然沒有什麼產出,毫無疑問最近的狀態就是不停的工做,不停的加班。因此仍是把最近工做進行一個總結,首先來我看看這段時間我作了什麼?php

工做內容

此次工做的主要內容就是「取消發貨單」功能,這個功能的上下文是這樣的:咱們支付成功的訂單會在一段時間後被拆成發貨單,本次開發任務的目的就是經過客戶端對用戶開放申請取消發貨單的功能。其實這個功能就是發貨單服務生成退款單以後回調訂單服務的一系列undo操做,其次這些邏輯咱們目前都是同步調用未異步隊列化,接着咱們來梳理下大體有哪些undo操做:閉包

undo訂單&訂單商品信息->undo商品庫存->undo各類促銷優惠活動的庫存->undo錢包餘額->log->消息

顯而易見這些操做基本和取消訂單的邏輯絕大多數一致,加之取消訂單的代碼已經很老了,並且可維護性,擴展性,可用性都不好,因此我又多了一項任務「重構取消訂單」。咱們接着來梳理下取消訂單的邏輯:框架

undo訂單&訂單商品信息->undo商品庫存->undo各類促銷優惠活動的庫存->生成退款單->undo錢包餘額->undo贈品->undo紅包->log->消息

下圖清晰的梳理了二者操做的內容:
圖片描述異步

建模

經過上面咱們對業務邏輯的梳理,其實這兩個功能絕大多數的邏輯是能夠公用的,且這每一個子邏輯均可以獨立成爲一個個體,這麼看來這就是典型的訂閱通知模型「觀察者模式」應用的場景。咱們能夠把「取消發貨單」和「取消訂單」當作一個被觀察或被訂閱的類實例的對象,一旦發生取消行爲,咱們當即通知各個觀察者作出相對應的行爲。原本php是提供了觀察者的接口SplSuject和SplObserver,咱們只需實現該接口便可,可是SplSuject的attach成員方法不支持閉包(使用閉包可使觀察者被通知的時候再實例化,節省了必定的性能和內存空間),因此我本身最後從新實現了該接口。最後咱們的模型以下:函數

圖片描述

填充業務邏輯

完成上面的建模,其實咱們的功能其實就算完成一半了,剩下的事情就是在每一個類文件填充對應獨立的業務邏輯便可。性能

/**
 * 被觀察者接口
 *
 * 因爲php原生的被觀察者接口SplSubject不支持註冊閉包,即本身實現一下這個接口
 */
Interface ObservableInterface
{
    /**
     * 註冊觀察者對象
     *
     * @param  Closure $closure 閉包形式註冊
     * @return void
     */
    public function attach(Closure $closure);

    /**
     * 剔除觀察者對象
     *
     * @param  ObserverInterface $observer 觀察者對象
     * @return void
     */
    public function detach(ObservableInterface $observer);

    /**
     * 通知觀察者對象
     *
     * @return void
     */
    public function notify();
}

/**
 * 觀察者接口
 *
 * php原生觀察者接口SplObserver
 */
Interface ObserverInterface
{
    /**
     * 觀察者操做
     *
     * @param  ObservableInterface $observable 被觀察者對象
     * @return void
     */
    public function operate(ObservableInterface $observable);
}
/**
 * 取消訂單被訂閱實體
 *
 * 被訂閱/被觀察者實體
 */
class Observable implements ObservableInterface
{
    /**
     * 註冊的觀察者/訂閱對象
     *
     * @var array
     */
    private $observers = [];

    /**
     * 已經被通知的觀察者/訂閱對象
     *
     * @var array
     */
    private $hadNotify = [];


    /**
     * 構造函數
     *
     * @return void
     */
    public function __construct(params...)
    {
        
    }

    /**
     * 註冊觀察者/訂閱對象
     *
     * @param  Closure $closure 閉包形式註冊
     * @return void
     */
    public function attach(Closure $closure)
    {
        $this->observers[] = $closure;
    }

    /**
     * 批量註冊觀察者/訂閱對象
     *
     * @param  array $closures 閉包形式註冊
     * @return void
     */
    public function multiAttach($closures = [])
    {
        $closures = array_filter($closures, function ($var) {
            if ($var instanceof Closure) {
                return $var;
            }
        });
        $this->observers = array_merge($this->observers, $closures);
    }

    /**
     * 剔除觀察者/訂閱對象
     *
     * @param  ObserverInterface $observer 觀察者對象/訂閱對象
     * @return void
     */
    public function detach(ObservableInterface $observer)
    {
        foreach ($this->observers as $k => $v) {
            if ($v() === $observer) {
                unset($this->observers[$k]);
            }
        }
    }

    /**
     * 通知觀察者/訂閱對象
     *
     * @return void
     */
    public function notify()
    {
        foreach ($this->observers as $v) {
            $instance = $v();
            if (in_array($instance, $this->hadNotify, true)) {
                // 不通知重複的訂閱
                continue;
            }
            $instance->operate($this);
            $this->hadNotify[] = $instance;
        }
    }
}

最後咱們在咱們的控制器類中完成調用以下:this

class OrderController
{
    /**
     * 取消訂單
     */
    public function cancel()
    {
        try {
            /* 建立取消訂單的被觀察者 */
            $subject = new Observable();

            // 註冊訂單觀察者
            $subject->attach(function () {
                return new Order();
            });

            // 註冊商品觀察者
            $subject->attach(function () {
                return new Goods();
            });

            // 註冊促銷商品觀察者
            $subject->attach(function () {
                return new PromotionGoods();
            });

            // 註冊退款單觀察者
            $subject->attach(function () {
                return new RefundOrder();
            });

            // 註冊錢包觀察者
            $subject->attach(function () {
                return new Wallet();
            });

            // 註冊紅包觀察者
            $subject->attach(function () {
                return new Bonus();
            });

            // 註冊贈品觀察者
            $subject->attach(function () {
                return new Gift();
            });

            // 註冊日誌觀察者
            $subject->attach(function () {
                return new Log();
            });

            // 註冊消息觀察者
            $subject->attach(function () {
                return new Notice();
            });

            /* 廣播 */
            $subject->notify();
        } catch (Exception $e) {
            # code...
        }
    }
}

class DeliveryController
{
    /**
     * 取消發貨單
     */
    public function cancel()
    {
        try {
            /* 建立取消發貨單的被觀察者 */
            $subject = new Observable();

            // 註冊訂單觀察者
            $subject->attach(function () {
                return new Order();
            });

            // 註冊商品觀察者
            $subject->attach(function () {
                return new Goods();
            });

            等等(不註冊紅包和贈品觀察者)...

            /* 廣播 */
            $subject->notify();
        } catch (Exception $e) {
            # code...
        }
    }
}

這樣的話咱們徹底高內聚鬆耦合了咱們的業務代碼,若是將來須要增長新的邏輯,咱們只須要註冊新的觀察者便可。這樣重構完成代碼後,咱們將來在取消訂單的時候只須要註冊訂單的觀察者到取消訂單的被觀察者便可,其餘的觀察者咱們再註冊到一個異步執行的取消訂單的被觀察者實例中,經過這樣咱們就能給用戶帶來好的體驗,用戶取消訂單的操做咱們只需通知訂單狀態變動,其他的觀察者咱們異步通知保證最終成功,在將來實現這個功能時咱們的業務代碼根本不須要動,只須要改變調用方式。spa

裝飾器模式

裝飾器思想,無論之前業務邏輯,甚至不去讀,調用以前的接口裝飾上新的數據,達到本身的目的。最近遇到的問題,在咱們的訂單列表加一些字段,可是訂單列表的代碼基本沒法閱讀和調整,最後想到了裝飾器的思想,最後咱們徹底不須要管以前的邏輯,咱們只需調用現有的類方法,再裝飾上咱們想要的數據便可,這樣就最簡化和快捷的達到了咱們的目的。日誌

Easy PHP:一個極速輕量級的PHP全棧框架

掃面下方二維碼關注個人技術公衆號,及時爲你們推送個人原創技術分享code

圖片描述

相關文章
相關標籤/搜索