設計模式停更了很久, 發現兜兜轉轉回來, 仍是離開不了那些個套路.php
今天咱們主要講解下 觀察者模式, 可能你聽這個名字感受很熟, 若是給你說下還能夠稱它爲 發佈訂閱 模式的話, 相信你對它就絕不陌生了.json
觀察者模式定義了一種一對多的依賴關係, 讓多個觀察者對象同時監聽某一個主題對象, 這個主題發生變化時, 通知全部的觀察對象. 總的來講, 觀察者是解除耦合的重要手段.設計模式
叮叮叮..安全
登錄代碼:bash
class Login
{
/**
* 處理登錄
*
* @return array
*/
public function handleLogin($param)
{
$isLogin = false;
//執行登錄
switch ($this->doLogin($param)) {
case 0:
$message = '登錄成功';
$isLogin = true;
break;
case 1:
$message = '賬號或密碼不對';
break;
case 2:
$message = '帳號已失效';
break;
default:
$message = '登錄失敗';
}
return [
'isLogin' => $isLogin,
'message' => $message,
];
}
/**
* 執行具體登錄操做
*
* @return int
*/
public function doLogin($param)
{
//dododo
return rand(0, 2);
}
}
複製代碼
這裏咱們爲了演示, 在實際執行登錄方法中隨機返回 0~2,對應返回不回的提示信息.微信
執行以下數據結構
$result = (new Login)->handleLogin(['email'=>'1350495180@qq.com','passwd'=>'123456']);
echo json_encode($result, JSON_UNESCAPED_UNICODE);
output:
{"isLogin":false,"message":"賬號或密碼不對"}
{"isLogin":true,"message":"登錄成功"}
{"isLogin":false,"message":"帳號已失效"}
複製代碼
叮叮叮...架構
產品經理: 登錄是實現了, 但咱們還須要一些數據用做分析, 這個需求一樣很簡單, 直接在上一次的登錄代碼那插入一個保存就能夠測試
小明: 好的呢經理.優化
public function handleLogin($param)
{
$isLogin = false;
//執行登錄
switch ($this->doLogin($param)) {
.....
}
$param['isLogin'] = $message;
$this->_saveLoginLog($param);
return [
'isLogin' => $isLogin,
'message' => $message,
];
}
/**
* 添加登錄日誌
*
* @param $param
* @return bool
*/
private function _saveLoginLog($param)
{
$param['client_ip'] = $this->get_real_ip();
$this->loginLogModel->insert($param);
return true;
}
複製代碼
叮叮叮...
開發至今, 我才發現這是一個愈來愈大的陷阱, 我沒有意識到這點, 即便一個簡單的登錄接口, 每次改完都得重測一遍, 一直變來變去, 思考着該怎麼去重構我得代碼.
class Login implements LoginSubjectInterface
{
private $observers;
public function __construct()
{
$this->observers = [];
}
/**
* 加入觀察者
*
* @param LoginObserverInterface $loginSubject
*/
public function attach(LoginObserverInterface $loginObserver)
{
$this->observers[] = $loginObserver;
}
/**
* 移除觀察者
*
* @param LoginObserverInterface $loginObserver
*/
public function detach(LoginObserverInterface $loginObserver)
{
//todo
}
/**
* 通知觀察者事件
*/
public function notify()
{
foreach ($this->observers as $observer) {
$observer->doNotify();
}
}
public function notify()
{
foreach ($this->observers as $observer) {
$observer->doNotify();
}
}
/**
* 處理登錄
*
* @return array
*/
public function handleLogin($param)
{
$isLogin = false;
//執行登錄
switch ($this->doLogin($param)) {
case 0:
$message = '登錄成功';
$isLogin = true;
break;
case 1:
$message = '賬號或密碼不對';
break;
case 2:
$message = '帳號已經被禁用';
break;
default:
$message = '登錄失敗';
}
$this->notify();
return [
'isLogin' => $isLogin,
'message' => $message,
];
}
}
複製代碼
登錄成功觀察者 interface
namespace App;
interface LoginObserverInterface
{
public function doNotify();
}
複製代碼
發送郵件登錄提醒類
class LoginEmailNotify implements LoginObserverInterface
{
private $loginSubject;
public function __construct(LoginSubjectInterface $loginSubject)
{
$this->loginSubject = $loginSubject;
$this->loginSubject->attach($this);
}
public function doNotify()
{
echo '發送郵件登錄提醒' . PHP_EOL;
}
}
複製代碼
發送短信通知類
class LoginDisable implements LoginObserverInterface
{
private $loginSubject;
public function __construct(LoginSubjectInterface $loginSubject)
{
$this->loginSubject = $loginSubject;
$this->loginSubject->attach($this);
}
public function doNotify()
{
echo '微信推送充值連接' . PHP_EOL;
}
}
複製代碼
測試
$loginObject = new Login();
new LoginEmailNotify($loginObject);
new LoginPhoneMsgNotify($loginObject);
$result = $loginObject->handleLogin(['email' => '1350495180@qq.com', 'passwd' => '123456']);
output:
發送郵件登錄提醒
發送短信通知
{"isLogin":false,"message":"帳號已經被禁用"}
複製代碼
咱們發現了在觀察者類中有一部分重複代碼, 每一個觀察者類中, 就是向被觀察者業務類執行 attch 操做, 這部分能夠抽出基類做爲封裝。另一點沒實現的就是 doNotify 必須做爲一個參數將當前登錄的賬號或手機號傳遞過去, 做爲發送短信依據.
咱們大部分使用觀察者都是使用推的模式, 被動接口 notify, 其實還有一種模式爲 拉 模式, 其實核心就是在 notify 中返向調用符合自身業務的接口去處理本身的邏輯.
SPL提供了一組標準數據結構, 下面使用了觀察者相關的 SplSubject、SplObserver兩種接口使用方式
subject 業務主類
use SplObserver;
use SplObjectStorage;
class Login implements \SplSubject
{
private $observers;
public function __construct()
{
$this->observers = new SplObjectStorage();
}
/**
* 加入觀察者
*
* @param SplObserver $loginSubject
*/
public function attach(SplObserver $loginObserver)
{
$this->observers->attach($loginObserver);
}
/**
* 移除觀察者
*
* @param SplObserver $loginObserver
*/
public function detach(SplObserver $loginObserver)
{
$this->observers->detach($loginObserver);
}
/**
* 通知觀察者事件
*/
public function notify()
{
foreach ($this->observers as $observer) {
$observer->update($this);
}
}
/**
* 處理登錄
*
* @return array
*/
public function handleLogin($param)
{
$isLogin = false;
//執行登錄
switch ($this->doLogin($param)) {
case 0:
$message = '登錄成功';
$isLogin = true;
break;
case 1:
$message = '賬號或密碼不對';
break;
case 2:
$message = '帳號已經被禁用';
break;
default:
$message = '登錄失敗';
}
$this->notify();
return [
'isLogin' => $isLogin,
'message' => $message,
];
}
/**
* 執行具體登錄操做
*
* @return int
*/
public function doLogin($param)
{
//dododo
return rand(0, 2);
}
}
複製代碼
郵件通知類
use SplObserver;
use SplSubject;
class LoginEmailNotify implements SplObserver
{
private $loginSubject;
public function __construct(SplSubject $loginSubject)
{
$this->loginSubject = $loginSubject;
$this->loginSubject->attach($this);
}
public function update(SplSubject $subject)
{
echo '發送郵件登錄提醒' . PHP_EOL;
}
}
複製代碼
短信通知類
use SplObserver;
use SplSubject;
class LoginPhoneMsgNotify implements SplObserver
{
private $loginSubject;
public function __construct(SplSubject $loginSubject)
{
$this->loginSubject = $loginSubject;
$this->loginSubject->attach($this);
}
public function update(SplSubject $subject)
{
echo '發送短信通知' . PHP_EOL;
}
}
複製代碼
以上代碼咱們使用了php spl內部封裝好的 SplSubject、SplObserver的接口, 以及 SplObjectStorage 對象存儲類. 固然在方便的同時也帶來了缺失部分靈活性, 例如通知觀察者只能實現 update 類接口.
小明和產品經理結局是?