觀察者模式
問題引入
一個(gè)系統(tǒng)在用戶登錄的時(shí)候,經(jīng)常要記錄一些東西:session、登錄次數(shù)、統(tǒng)計(jì)在線時(shí)長(zhǎng)等;如果這么多的操作按照面向過程的方法編寫,使一個(gè)對(duì)象變得復(fù)雜,它要操作這么多的事情,這樣也違反單一功能原則。
如果這些操作以插件形式加載或移除,那么登錄只完成它的單一功能操作。
- 這樣就可以使用觀察者模式,后期再增加相關(guān)功能模塊,不需要太多工作就可以加載同步;也不用修改登錄的功能(符合開閉原則);
- 缺點(diǎn)就是因?yàn)楦鱾€(gè)插件模塊分散,后面功能可能重疊之前的功能,導(dǎo)致數(shù)據(jù)錯(cuò)誤,所以在使用時(shí)要盡量注釋或者整理好文檔
使用場(chǎng)景
- 當(dāng)一個(gè)抽象模型有兩個(gè)方面,其中一個(gè)方面依賴于另一個(gè)方面。
- 當(dāng)對(duì)一個(gè)對(duì)象的改變需要同時(shí)改變其它對(duì)象,而不知道具體有多少個(gè)對(duì)象待改變。
- 當(dāng)一個(gè)對(duì)象必須通知其它對(duì)象,而它又不能假定其它對(duì)象是誰。換句話說,你不希望這些對(duì)象是緊密耦合的
如何使用
php已經(jīng)提供了主題接口類和觀察者接口類,還有一個(gè)SplObjectStorage數(shù)據(jù)結(jié)構(gòu)對(duì)象容器——實(shí)現(xiàn)了Countable,Iterator,Serializable,ArrayAccess四個(gè)接口??蓪?shí)現(xiàn)統(tǒng)計(jì)、迭代、序列化、數(shù)組式訪問等功能。
- 主題類(被觀察者)
namespace Observer;
use SplObjectStorage;
use SplObserver;
use SplSubject;
class User implements SplSubject
{
/**
* @var SplObjectStorage
*/
private $observers;
public function __construct()
{
$this->observers = new SplObjectStorage();
}
/**
* Attach an SplObserver
* @link http://php.net/manual/en/splsubject.attach.php
* @param SplObserver $observer <p>
* The <b>SplObserver</b> to attach.
* </p>
* @return void
* @since 5.1.0
*/
public function attach(SplObserver $observer)
{
$this->observers->attach($observer);
}
/**
* Detach an observer
* @link http://php.net/manual/en/splsubject.detach.php
* @param SplObserver $observer <p>
* The <b>SplObserver</b> to detach.
* </p>
* @return void
* @since 5.1.0
*/
public function detach(SplObserver $observer)
{
$this->observers->detach($observer);
}
/**
* Notify an observer
* @link http://php.net/manual/en/splsubject.notify.php
* @return void
* @since 5.1.0
*/
public function notify()
{
foreach ($this->observers as $observer) {
$observer->update($this);
}
}
}
- 觀察者1
namespace Observer;
use SplObserver;
use SplSubject;
class UserObserver implements SplObserver
{
/**
* Receive update from subject
* @link http://php.net/manual/en/splobserver.update.php
* @param SplSubject $subject <p>
* The <b>SplSubject</b> notifying the observer of an update.
* </p>
* @return string
* @since 5.1.0
*/
public function update(SplSubject $subject)
{
echo '我是觀察者1:獲取用戶信息' . '<br>';
}
}
- 觀察者2
namespace Observer;
use SplSubject;
class OntimeObserver implements \SplObserver
{
/**
* Receive update from subject
* @link http://php.net/manual/en/splobserver.update.php
* @param SplSubject $subject <p>
* The <b>SplSubject</b> notifying the observer of an update.
* </p>
* @return string
* @since 5.1.0
*/
public function update(SplSubject $subject)
{
echo '我是觀察者2:記錄用戶在線時(shí)間' .'<br>';
}
}
- 實(shí)施調(diào)用
use Observer\OntimeObserver;
use Observer\User;
use Observer\UserObserver;
spl_autoload_register(function($class_name) {
$class_file = realpath(dirname(__FILE__)."/../") .'/' . str_replace('\\','/', $class_name) . '.php';
if (file_exists($class_file)) {
include $class_file;
} else {
echo "加載文件失敗";
}
});
//實(shí)例化主題類
$user = new User();
//添加觀察者
$user->attach(new UserObserver());
$user->attach(new OntimeObserver());
//通知觀察者
$user->notify();