设计模式历险记之观察者模式

观察者模式也叫做发布订阅模式,在具体分析它之前我们先来看看书本上给出的定义。

定义:定义了一种一对多的依赖关系,让多个观察者对象监听某个主题对象。这个主题对象状态发生变化时,会通知所有观察者对象,使他们能够自动跟新自己。

一.结构图:

这里的结构图有两种,一种是实体观察者关联实体主题,一种是两者间没有关联:


实体主题和实体观察者有关联。


实体主题和实体观察者无关联。

二.举个栗子:



现在有这样一种场景:在一个电商平台,里面分了卖家,买家,平台方,客服人员。如果买家对某一件商品下单并付款成功,需要通知对应的卖家,买家,平台方和对应的客服人员。

我们先按照正常的面向过程的思维来写一下代码:

<?php
echo '下单付款成功';
echo '通知卖家';
echo '通知买家';
echo '通知平台'; //比如说通知平台的财务系统,已经付款了
echo '通知客服';

上面的代码就使先下单付款,在下单付款的代码后一次写通知每个角色用户的代码,这样一看是不是就感觉坏味道出来了,上边的通知代码不能复用,并且有很强的耦合性。

接下来我们观察者模式来写一下:

<?php
class SaleGoods{
    private $notifysObj = [];

    private $status = '出售中';
    
    public function setStatus(){
        $this->status = '打折商品被下单并付款';
        $this->notity();
    }

    public function getStatus(){
        return $this->status;
    }

    public function setNocityObj(people $people){
        array_push($this->notifysObj, $people);
    }

    private function notity(){
        foreach ($this->notifysObj as $notifyObj) {
            $notifyObj->watch();
        }
    }
}

abstract class People{

    protected $watchTheme;

    public function __construct(SaleGoods $SaleGoods){
        $this->watchTheme = $SaleGoods;
    }

    public abstract function watch();
}

class Buyer extends People{

    public function buy(SaleGoods $goods){
        $goods->setStatus();
    }

    public function watch(){
        echo "我是买家,{$this->watchTheme->getStatus()}".PHP_EOL;
    }
}

class Seller extends People{
    public function watch(){
        echo "我是卖家,{$this->watchTheme->getStatus()}".PHP_EOL;
    }
}

class Terrace extends People{
    public function watch(){
        echo "我是平台方,{$this->watchTheme->getStatus()}".PHP_EOL;
    }
}

class Service extends People{
    public function watch(){
        echo "我是客服,{$this->watchTheme->getStatus()}".PHP_EOL;
    }
}


$goods = new SaleGoods();
$people = new Buyer($goods);
$goods->setNocityObj($people);
$goods->setNocityObj(new Seller($goods));
$goods->setNocityObj(new Terrace($goods));
$goods->setNocityObj(new Service($goods));

$people->buy($goods);

这里的买家,卖家,客户,平台方就属于观察者,商品算是一个主题对象。主题对象状态发生变化的时候同时主动通知观察者,观察者收到消息后做出一系列动作,但是上边的代码还是不足,具体的产品和观察者还有有耦合,这里每个观察者都注入了具体的主题对象,这样不好,我们应该把具体的对象抽象成类,把注入具体对象变成注入抽象类,这样观察者和主题就解耦了。好,看下民的代码。

<?php
//抽象出的商品类
abstract class Goods{
    private $notifyObj=[];
    //给主题添加观察者,依赖于抽象
    public abstract function attach(People $people);
    //删除主题中的观察者
    public abstract function detach(People $people);
    //通知观察者
    public abstract function notify();
}

class OtherGoods extends Goods{
    private $notifysObj = [];
    
    private $status = '出售中';

    public function setStatus(){
        $this->status = '其它商品被下单并付款';
        $this->notify();
    }

    public function getStatus(){
        return $this->status;
    }
    
    public function attach(People $people){
        array_push($this->notifysObj,$people);
    }
    
    public function detach(People $people){
        $key = array_search($people,$this->notifysObj);
        unset($this->notifysObj[$key]);
    }
    
    public function notify(){
        foreach($this->notifysObj as $notifyObj){
            $notifyObj->watch();
        }
    }
    
}

class SaleGoods extends Goods{
    private $notifysObj = [];

    private $status = '出售中';
    
    public function setStatus(){
        $this->status = '打折商品被下单并付款';
        $this->notify();
    }

    public function getStatus(){
        return $this->status;
    }

    public function attach(People $people){
        array_push($this->notifysObj,$people);
    }
    
    public function detach(People $people){
        $key = array_search($people,$this->notifysObj);
        unset($this->notifysObj[$key]);
    }
    
    public function notify(){
        foreach($this->notifysObj as $notifyObj){
            $notifyObj->watch();
        }
    }

}

//抽象出的平台使用者类
abstract class People{

    protected $watchTheme;
    
    //观察者依赖主题的抽象
    public function __construct(Goods $goods){
        $this->watchTheme = $goods;
    }
    //观察方法,接受主题类的通知。
    public abstract function watch();
}


class Buyer extends People{

    public function buy(Goods $goods){
        $goods->setStatus();
    }

    public function watch(){
        echo "我是买家,{$this->watchTheme->getStatus()}".PHP_EOL;
    }
}

class Seller extends People{
    public function watch(){
        echo "我是卖家,{$this->watchTheme->getStatus()}".PHP_EOL;
    }
}

class Terrace extends People{
    public function watch(){
        echo "我是平台方,{$this->watchTheme->getStatus()}".PHP_EOL;
    }
}

class Service extends People{
    public function watch(){
        echo "我是客服,{$this->watchTheme->getStatus()}".PHP_EOL;
    }
}


$goods = new OtherGoods(); //实例化一个主题类,有了主题类观察者才能观察
//实例化买家,其实也是实例化了一个观察者,只是这个观察者还有其它的功能。
$people = new Buyer($goods);
$goods->attach($people);
$goods->attach(new Seller($goods));
$goods->attach(new Terrace($goods));
$goods->attach(new Service($goods));
$goods->detach($people);

$people->buy($goods);//用户下单付款,改变商品状态,同时通知各个用户

抽象出一个商品的主题类,抽象出一平台用户观察者类,具体的产品都继承商品抽象类,具体的用户都继承用户类,这样都依赖于抽象,符合依赖倒置原则。这样,我们就用观察者模式完成了这个需求。

三.总结



先说说作者自己对观察者模式错误的理解:
从观察者模式这个名字,我第一反应理解的就是有一个观察者始终在轮询的查询被观察者状态是否改变,这个思路让我吃了很多苦头,始终没有想出来这样代码该怎么写,后面才发现这个想法其实是错误的。

然后再说说作者现在对它的理解:
当多个模块依赖某个模块的时候,这个时候可以将多个模块注册到依赖的那个被依赖的模块中,然后当被依赖的模块发生变化的时候,通知依赖模块,依赖模块接受到通知后去做出对应的动作。

下面我举个例子来让大家理解:
我们知道观察者模式又叫做发布订阅模式,那么什么是发布?什么是订阅呢?就拿我们抢购小米手机来说吧,小米手机一般开了发布会后,都会告诉各位米粉,十号下午六点就接受预定,十三号上午十点首发。米粉知道这一消息后就在十号下午六点去小米官网预定,等到了十三号上午六点小米就有优先卖给有预定的米粉。
这里我们把米粉理解成观察者,也称为订阅者。小米理解成主题对象,也称发布者。当小米手机状态从预定变为出售(主题对象状态变化),正式出售的时候(主题通知观察者),它出售给谁(主题应该通知给谁)呢?是不是出售给有预定的米粉(主题应该通知在主题对象中注册过的对象),所以米粉预定的过程就是订阅(向主题对象中注册观察者)。

后面我会把 laravel 框架中的事件系统拿出来为大家分析。

设计模式一直存在于我们的生活中。

Snail's Blog
请先登录后发表评论
  • 最新评论
  • 总共0条评论
  • 本博客使用免费开源的 laravel-bjyblog v5.5.1.3 -develop 搭建 © 2014-2018 www.snail-c.cn 版权所有 ICP证:蜀ICP备18023253号-1
  • 联系邮箱:459921737@qq.com