场景:一个气象站点,当温度、湿度或气压发生改变时都要像订阅了该气象服务的用户推送提醒,假设用户拥有三种电子产品:mac、iphone和apple watch。
第一版的设计代码如下:
WeatherData.php
<?php
/**
* 气象站类,用于提供气象数据,数据变化时及时推送至用户的设备
*
* @author ben
*
*/
class WeatherData{
/**
* 气温
* @var string
*/
private $_temperature;
/**
* 气压
* @var string
*/
private $_pressure;
/**
* 湿度
* @var string
*/
private $_humidity;
/**
* 用户设备之mac
* @var object
*/
private $_mac;
/**
* 用户设备之iphone
* @var object
*/
private $_iphone;
/**
* 用户设备之watch(壕啊,咱们做朋友吧。。。)
* @var object
*/
private $_iwatch;
/**
* 初始化用户设备
* @param object $mac
* @param object $iphone
* @param object $iwatch
*/
public function __construct($mac, $iphone, $iwatch){
$this->_mac = $mac;
$this->_iphone = $iphone;
$this->_iwatch = $iwatch;
}
/**
* 当数据变更时向用户推送即时数据
*/
public function dataChanged(){
$this->_temperature = $this->getTemperature();
$this->_humidity = $this->getHumidity();
$this->_pressure = $this->getPressure();
$this->_mac->update($this->_humidity, $this->_temperature, $this->_pressure);
$this->_iphone->update($this->_humidity, $this->_temperature, $this->_pressure);
$this->_iwatch->update($this->_humidity, $this->_temperature, $this->_pressure);
}
}
代码的坏味道:
气象站类WeatherData与用户的设备类mac、iphone、iwatch类之间存在着强耦合关系,而真实的场景中用户可能随时会取消业务,或者为新的电子产品订阅该业务,这就意味着我们今后将会频繁的修改这个文件中的代码。
下面我们来看看观察者模式是怎么为我们解决这个难题的。
要理解观察者模式,可以结合真实的案例,比如说订阅报纸,仔细想想在这样的场景中存在哪几种元素:
1、报纸的发布者;
2、报纸的订阅者;
发布者与订阅者组成了观察者的两个基本要素。具体用uml类图描述如下:
具体的实现代码:
Subject.php
<?php
abstract class Subject{
protected $observers;
public function addObserver($key, Equipment $observer){
$this->observers[$key] = $observer;
}
public function removeObserver($key){
if(isset($this->observers[$key])){
unset($this->observers[$key]);
}
}
abstract function notify();
// foreach ($this->observer as $observer){
// $observer->update();
// }
}
Equipment.php
<?php
abstract class Equipment{
abstract function update($temperature, $pressure, $humidity);
abstract function display();
}
WeatherData.php
<?php
require_once 'Subject.php';
class WeatherData extends Subject{
private $_temperature;
private $_pressure;
private $_humidity;
/* (non-PHPdoc)
* @see Subject::notify()
*/
public function notify() {
foreach ($this->observers as $observer){
$observer->update($this->_temperature, $this->_pressure, $this->_humidity);
}
}
public function dataChange($temperature, $pressure, $humidity){
$this->_temperature = $temperature;
$this->_pressure = $pressure;
$this->_humidity = $humidity;
$this->notify();
}
}
Mac.php
<?php
require_once 'Equipment.php';
class Mac extends Equipment{
private $_temperature;
private $_pressure;
private $_humidity;
/* (non-PHPdoc)
* @see Equipment::update()
*/
public function update($temperature, $pressure, $humidity) {
// TODO Auto-generated method stub
$this->_temperature = $temperature;
$this->_pressure = $pressure;
$this->_humidity = $humidity;
$this->display();
}
/* (non-PHPdoc)
* @see Equipment::display()
*/
public function display() {
// TODO Auto-generated method stub
echo "Current conditions:<br />
temperature:$this->_temperature;<br />
pressure:$this->_pressure;<br />
humidity:$this->_humidity;<br />
--From Mac client<br />";
}
}
Iwatch.php
<?php
require_once 'Equipment.php';
class Iwatch extends Equipment{
private $_temperature;
private $_pressure;
private $_humidity;
/* (non-PHPdoc)
* @see Equipment::update()
*/
public function update($temperature, $pressure, $humidity) {
// TODO: Auto-generated method stub
$this->_temperature = $temperature;
$this->_pressure = $pressure;
$this->_humidity = $humidity;
$this->display();
}
/* (non-PHPdoc)
* @see Equipment::display()
*/
public function display() {
// TODO Auto-generated method stub
echo "Current conditions:<br />
temperature:$this->_temperature;<br />
pressure:$this->_pressure;<br />
humidity:$this->_humidity;<br />
--From Iwatch client<br />";
}
}
Iphone.php
<?php
require_once 'Equipment.php';
class Iphone extends Equipment{
private $_temperature;
private $_pressure;
private $_humidity;
/* (non-PHPdoc)
* @see Equipment::update()
*/
public function update($temperature, $pressure, $humidity) {
// TODO Auto-generated method stub
$this->_temperature = $temperature;
$this->_pressure = $pressure;
$this->_humidity = $humidity;
$this->display();
}
/* (non-PHPdoc)
* @see Equipment::display()
*/
public function display() {
// TODO Auto-generated method stub
echo "Current conditions:<br />
temperature:$this->_temperature;<br />
pressure:$this->_pressure;<br />
humidity:$this->_humidity;<br />
--From Iphone client<br />";
}
}
index.php
<?php
require_once 'WeatherData.php';
require_once 'Iwatch.php';
require_once 'Iphone.php';
require_once 'Mac.php';
$subject = new WeatherData();
$observer1 = new Iwatch();
$observer2 = new Iphone();
$observer3 = new Mac();
$subject->addObserver('Iwatch', $observer1);
$subject->addObserver('Iphone', $observer2);
$subject->addObserver('Mac', $observer3);
$subject->dataChange('38摄氏度', '气压好高啊', '很湿润');
运行结果是:
Current conditions:
temperature:38摄氏度;
pressure:气压好高啊;
humidity:很湿润;
--From Iwatch client
Current conditions:
temperature:38摄氏度;
pressure:气压好高啊;
humidity:很湿润;
--From Iphone client
Current conditions:
temperature:38摄氏度;
pressure:气压好高啊;
humidity:很湿润;
--From Mac client
当然php已经帮我们准备好了两个接口,我们没有必要重造轮子,这两个接口分别是SplObserver和SplSubject,关于这两个接口的定义,可以查看以下官方文档:
SplObserver:http://php.net/manual/zh/class.splobserver.php
SplSubject:http://php.net/manual/zh/class.splsubject.php
使用这两个接口之后我们进一步修改后的代码清单如下:
index.php
<?php
require_once 'WeatherData.php';
require_once 'Iwatch.php';
require_once 'Iphone.php';
require_once 'Mac.php';
$subject = new WeatherData();
$observer1 = new Iwatch();
$observer2 = new Iphone();
$observer3 = new Mac();
$subject->attach($observer1);
$subject->attach($observer2);
$subject->attach($observer3);
$subject->dataChange('38摄氏度', '气压好高啊', '很湿润');
<?php
class Iphone implements SplObserver{
private $_temperature;
private $_pressure;
private $_humidity;
/* (non-PHPdoc)
* @see Equipment::update()
*/
public function update(SplSubject $subject) {
// TODO Auto-generated method stub
$this->_temperature = $subject->getTemperature();
$this->_pressure = $subject->getPressure();
$this->_humidity = $subject->getHumidity();
$this->display();
}
/* (non-PHPdoc)
* @see Equipment::display()
*/
public function display() {
// TODO Auto-generated method stub
echo "Current conditions:<br />
temperature:$this->_temperature;<br />
pressure:$this->_pressure;<br />
humidity:$this->_humidity;<br />
--From Iphone client<br />";
}
}
<?php
class Iwatch implements SplObserver{
private $_temperature;
private $_pressure;
private $_humidity;
/* (non-PHPdoc)
* @see Equipment::update()
*/
public function update(SplSubject $subject) {
// TODO Auto-generated method stub
$this->_temperature = $subject->getTemperature();
$this->_pressure = $subject->getPressure();
$this->_humidity = $subject->getHumidity();
$this->display();
}
/* (non-PHPdoc)
* @see Equipment::display()
*/
public function display() {
// TODO Auto-generated method stub
echo "Current conditions:<br />
temperature:$this->_temperature;<br />
pressure:$this->_pressure;<br />
humidity:$this->_humidity;<br />
--From Iwatch client<br />";
}
}
Mac.php
<?php
class Mac implements SplObserver{
private $_temperature;
private $_pressure;
private $_humidity;
/* (non-PHPdoc)
* @see Equipment::update()
*/
public function update(SplSubject $subject) {
// TODO Auto-generated method stub
$this->_temperature = $subject->getTemperature();
$this->_pressure = $subject->getPressure();
$this->_humidity = $subject->getHumidity();
$this->display();
}
/* (non-PHPdoc)
* @see Equipment::display()
*/
public function display() {
// TODO Auto-generated method stub
echo "Current conditions:<br />
temperature:$this->_temperature;<br />
pressure:$this->_pressure;<br />
humidity:$this->_humidity;<br />
--From Mac client<br />";
}
}
WeatherData.php
<?php
class WeatherData implements SplSubject{
protected $observers;
private $_temperature;
private $_pressure;
private $_humidity;
/* (non-PHPdoc)
* @see Subject::notify()
*/
public function notify() {
foreach ($this->observers as $observer){
$observer->update($this);
}
}
public function dataChange($temperature, $pressure, $humidity){
$this->_temperature = $temperature;
$this->_pressure = $pressure;
$this->_humidity = $humidity;
$this->notify();
}
public function getTemperature(){
return $this->_temperature;
}
public function getPressure(){
return $this->_pressure;
}
public function getHumidity(){
return $this->_humidity;
}
/* (non-PHPdoc)
* @see SplSubject::attach()
*/
public function attach(SplObserver $observer) {
// TODO Auto-generated method stub
$this->observers[] = $observer;
}
/* (non-PHPdoc)
* @see SplSubject::detach()
*/
public function detach(SplObserver $observer) {
// TODO Auto-generated method stub
$key = array_search($observer, $this->observers, true);
if($key){
unset($this->observers[$key]);
}
}
}