PHPer月工作总结之观察者&装饰器模式

TIGERB

前言

还是每月的目标至少写一篇文章,一晃八月份就要过去了,这个月依然没有什么产出,毫无疑问最近的状态就是不停的工作,不停的加班。所以还是把最近工作进行一个总结,首先来我看看这段时间我做了什么?

工作内容

这次工作的主要内容就是“取消发货单”功能,这个功能的上下文是这样的:我们支付成功的订单会在一段时间后被拆成发货单,本次开发任务的目的就是通过客户端对用户开放申请取消发货单的功能。其实这个功能就是发货单服务生成退款单之后回调订单服务的一系列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;
        }
    }
}

最后我们在我们的控制器类中完成调用如下:

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...
        }
    }
}

这样的话我们完全高内聚松耦合了我们的业务代码,如果未来需要增加新的逻辑,我们只需要注册新的观察者即可。这样重构完成代码后,我们未来在取消订单的时候只需要注册订单的观察者到取消订单的被观察者即可,其他的观察者我们再注册到一个异步执行的取消订单的被观察者实例中,通过这样我们就能给用户带来好的体验,用户取消订单的操作我们只需通知订单状态变更,其余的观察者我们异步通知保证最终成功,在未来实现这个功能时我们的业务代码根本不需要动,只需要改变调用方式。

装饰器模式

装饰器思想,不管以前业务逻辑,甚至不去读,调用之前的接口装饰上新的数据,达到自己的目的。最近遇到的问题,在我们的订单列表加一些字段,但是订单列表的代码基本无法阅读和调整,最后想到了装饰器的思想,最后我们完全不需要管之前的逻辑,我们只需调用现有的类方法,再装饰上我们想要的数据即可,这样就最简化和快捷的达到了我们的目的。

Easy PHP:一个极速轻量级的PHP全栈框架

扫面下方二维码关注我的技术公众号,及时为大家推送我的原创技术分享

图片描述

阅读 2.6k

TIGERB的技术博客
关于web开发的基础回顾与学习

// Trying to be the person you want to be.

9.4k 声望
2.6k 粉丝
0 条评论
你知道吗?

// Trying to be the person you want to be.

9.4k 声望
2.6k 粉丝
文章目录
宣传栏