愿你坚持不懈,努力进步,进阶成自己理想的人

—— 2017.09, 写给3年后的自己

设计模式之观察者模式

一、使用场景

观察者模式,也叫做发布者订阅者模式,是常用的一个设计模式,实际应用开发中经常可以看到观察者模式的影子,如JavaScript中的事件监听就是运用到了观察者模式的思想。观察者模式适用于 当一个对象的改变需要同时改变其他对象,且对象的改变不知道有多少其他对象待改变的时候,就可以使用观察者模式了

二、基本角色

  • 通知者(发布者):做出一个状态的改变,并将状态的改变传播到其他对象上
  • 观察者(订阅者):受通知者状态改变而执行相应动作的对象

三、实现

这里,我使用PHP实现观察者模式,为了解除耦合,用到了PHP的interface类型约束,通知者和观察者都依赖于抽象,使用接口实现,如:

interface Subject {
    public function getStatus();
    public function setStatus(Observer observer);
    public function attach(Observer observer); // 添加一个新的观察者(订阅)
    public function detach(Observer observer); // 删除一个观察者(解除订阅)
    public function notify();                  // 通知观察者
}

interface Observer {
    public function update(); // 当接收到状态改变的时候,要执行的动作
}

这里,我们假设一个场景:某公司各部门的工作听命于公司老板,这就是一种一个对象的状态影响到其他对象的例子,老板的状态(如一个命令),各部门都需要响应。在这种场景下,老板是通知者(发布状态),其他部门的是观察者(响应状态的改变),那么可以实现如下:

class Boss implements Subject {
    private $status;
    // 观察者列表
    private $observerList = array();

    public function setStatus($status) {
        $this->status = $status;
    }

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

    public function attach(Observer $observer) {
        if(!in_array($observer, $this->observerList)) {
            array_push($this->observerList, $observer);
        }
    }

    public function detach(Observer $observer) {
        foreach($this->observerList as $key => $eachObserver) {
            if($observer == $eachObserver) {
                unset($this->observerList[$key]);
                break;
            }
        }
    }

    public function notify() {
        foreach($this->observerList as $eachObserver) {
            $eachObserver->update();
        }
    }
}

其他部门,如秘书部和董事会:

class Secretary implements Observer {
    private $subject;

    function __construct(Subject $subject) {
        $this->subject = $subject;
    }

    function update() {
        echo $this->subject->getStatus().",马上准备材料!";
        echo PHP_EOL;
    }
}

class Board implements Observer {
    private $subject;

    function __construct(Subject $subject) {
        $this->subject = $subject;
    }

    function update() {
        echo $this->subject->getStatus().",记得准时到场!";
        echo PHP_EOL;
    }
}

然后,老板通知今天开重要会议,就可以如下实现:

$boss = new Boss;

$boss->attach(new Secretary($boss));
$boss->attach(new Board($boss));

// 做出状态改变
$boss->setStatus("下午有个重要会议要开");
$boss->notify();

/*
运行结果:
下午有个重要会议要开,马上准备材料!
下午有个重要会议要开,记得准时到场!
*/