本小节是设计模式中的第二篇,观察者模式。先不说定义,先看看观察者模式适用的场景,当你觉得这些场景很常见时,而且使用观察者模式之后,使得代码看起来更优雅,估计就会引起你的重视,才会学的更加深刻。

    场景一(未使用观察者模式)

        用户注册:注册成功后,需要给用户发邮件、发短信;失败,需要将错误信息记录到日志。

// User.php
class User
{
public function register(Request $request)
{
$postData = $request->input();
// parameters check ...
try{
$user = App\User::create($postData);
// 注册成功
// 发送邮件
try{
( new Email() )->sendEmail($user);
} catch(EmailException $e) {
// ...
}
// 发送短信
try{
( new Sms() )->sendSms($user);
} catch(SmsException $e) {
// ...
}
}catch(PDOException $e) {
( new Log() )->write2Log($e);
}
}
}

    代码的核心业务是用户注册,但是在不知不觉中,引入了各项逻辑的处理。使代码耦合度增大,哪一天,如果新增邀请功能,被邀请人成功注册后,邀请人将获得相应的积分。怎么办?项目已经上线了,继续在注册成功的逻辑里面写积分功能?当然可以,但是不利于维护和优雅的定义,也不利于产品迭代。此时,如果有一种办法或设计模式,能够在不断的修改需求的前提,快速的完成迭代,而且使代码看起来更加优雅,和业务逻辑的解耦,不正是我们需要的吗?

    观察者模式(订阅-发布模式)  

        个人理解为,观察者模式是面向行为的,即一个行为(事件)发生,所有依赖此行为的事件,将自动被触发。官方点说,就是一个主题,一个(或多个)观察者,主题可以理解为处理核心业务的代码(用户的注册),观察者是依赖于主题而产生的事件(发送邮件等)。观察者模式通常由两个接口和两个或以上的类组成。下面来看下用观察者模式来重构下上面的代码。

<?php
/**
* 定义一个观察者接口,所有的观察者实例必须实现update方法
*/
interface UserObserver
{
/**每个观察者必须实现
* @DateTime 2019-09-08
* @param [object] $sender [主题对象]
* @param [array] $args [参数]
* @return [mix]
*/
public function update($sender, $args);
}
<?php
/**
* 主题接口
* 所有被观察的主题必须实现 addObserver 方法
*/
interface UserObservable
{
/**
* 参数 $observe 必须是继承观察者的接口【类型约束】
*/
public function addObserver(UserObserver $observer);
}
<?php
require_once('./UserObservable.php');
require_once('./Email.php');

/**
* 主题(用户逻辑的核心代码)
*/
class User implements UserObservable
{
private $_observers = [];

// 必须是实现UserObserve接口的类
public function addObserver(UserObserver $observer)
{
$this->_observers[] = $observer;
}

public function register(Request $request)
{ $postData = $request->input(); // 模拟 laravel 框架接受参数
// parameters check ...
// todo 注册逻辑
$user = User::create($postData);
// 注册成功,循环通知每个观察者
foreach ($this->_observers as $observer) {
$observer->update($this, $user);
}
}
}
$user = new User();
// 添加观察者(该观察者必须实现UserObserver)
$user->addObserver(new Email());
// 注册
$user->register(['user_name'=>'boris', 'pwd'=>'****']);
<?php
require_once('./UserObserver.php');
/**
* Email 通知
* 必须实现UserObserver,相当于订阅了用户行为(包含但不限于注册)
*/
class Email implements UserObserver
{
protected $_to = '';

// 观察者核心处理方法
public function update($sender, $user)
{
echo '恭喜:'.$user->user_name.'注册成为我们的会员';
}
}

    其中,接口 UserObservable 是规范约束需要观察者的实例(这里的用户注册);User 主题,正是实现 UserObservable 接口;接口 UserObserver 是规范约束观察者的实例(这里的邮件发送);Email 观察者,当用户触发注册的行为时,该方法在注册成功后自动被执行。好长的一段篇幅介绍观察者模式,但是这些代码看上去比刚刚流水式代码麻烦也复杂好多,是的,如果业务足够简单,完全没必要弄复杂。但是,以后业务逻辑复杂后,观察者模式的优势就凸显出来了。

    优势 :内部解耦、快速迭代、方便测试
/**
* 不用动注册的核心代码【内部解耦】
* 新增短信通知注册用户
*/
// 添加短信观察者
$user->addObserver(new SMS());

class SMS implements UserObserver
{
// ...
}

    好了,以上就是观察者模式的入门介绍。但是,代码看上去确实复杂,还得写两个接口。其实,php 已经为我们提供了内置的相关的 Spl 扩展。

/**
* The <b>SplObserver</b> interface is used alongside
* <b>SplSubject</b> to implement the Observer Design Pattern.
* @link http://php.net/manual/en/class.splobserver.php
*/
interface 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 void
* @since 5.1.0
*/
public function update (SplSubject $subject);

}

/**
* The <b>SplSubject</b> interface is used alongside
* <b>SplObserver</b> to implement the Observer Design Pattern.
* @link http://php.net/manual/en/class.splsubject.php
*/
interface SplSubject {

/**
* 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);

/**
* 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);

/**
* Notify an observer
* @link http://php.net/manual/en/splsubject.notify.php
* @return void
* @since 5.1.0
*/
public function notify ();

}

    其中,SplSubject 相当于我们的 UserObservable,比我们多了移除观察者和处理的方法。SplObserver 相当于 UserObserver。特别注意的是,SplObserver 接口中的 update 方法的参数必须是 SplSubject $subject。接下来,我们使用内置的 Spl 类,重构下我们的观察者类。

    主题 User.php (依旧是用户注册)
<?php
class User implements SplSubject
{
private $_observers = [];
public $email = '';
public $email_content = '';

public function attach(SplObserver $observer)
{
$this->_observers[] = $observer;
}

public function detach(SplObserver $observer)
{
if (array_search($observer, $this->_observers, true) != $index) {
unset($this->_observers[$index]);
}
}

public function notify()
{
if(!empty($this->_observers) && $this->email) {
foreach($this->_observers as $observer) {
$observer->update($this);
}
}
}

public function register(Request $request)
{
$postData = $request->input();
$this->email = $postData['email'];
// parameters check ...
$register = true; // 模拟用户注册成功
$this->email_content = '恭喜您注册成功';
// 通知观察者
$this->notify();
return $register;
}
}
    观察者(邮件发送)
<?php
class Email implements SplObserver
{
// 观察者核心处理方法
public function update(SplSubject $subject)
{
$this->send($subject->email, $subject->email_content);
}

public function send($email, $content)
{
// 调用邮件发送
echo $email.' '.$content;
}
}
    测试
// 实例化用户对象
$user = new User();
// 添加观察者
$user->attach( new Email() );
// 注册
$user->register(['userName'=>'boris', 'email'=>'895207458@qq.com']);


         由于能力有限,不足或有不解之处,希望在下方评论区与我讨论,共同提高 。

        无论何时何地,云聚云散皆是美!只是美的形态不一样而已~