设计模式-观察者模式(observer)
观察者模式又叫做发布-订阅(Publish/Subscribe)模式。
定义
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
什么时候使用观察者模式?
1)当一个对象的改变需要同时改变其他对象的时候,而且它不知道具体有多少对象有待改变时,应该考虑使用观察者模式。
2)当一个抽象模型有两方面,其中一方面依赖另一方面,这时用观察者模式可以将这两者封装在独立的对象中,使它们各自独立地改变和复用。
应用场景
1)订单支付成功后,会做各种动作,比如发送EMAIL,或者改变订单状态,发送短信给客户,修改优惠券,通知仓库订单号等等
2)网站登录之后,修改登录时间,推送最新活动,让附近的人给他打招呼
在观察者模式中,又分为推模型和拉模型两种方式
1)推模型是假定主题对象知道观察者需要的数据
2)拉模型是主题对象不知道观察者具体需要什么数据,没有办法的情况下,干脆把自身传递给观察者,让观察者自己去按需要取值。
两种方式的比较:推模型可能会使得观察者对象难以复用,因为观察者的update()方法是按需要定义的参数,可能无法兼顾没有考虑到的使用情况。这就意味着出现新情况的时候,就可能提供新的update()方法,或者是干脆重新实现观察者;而拉模型就不会造成这样的情况,因为拉模型下,update()方法的参数是主题对象本身,这基本上是主题对象能传递的最大数据集合了,基本上可以适应各种情况的需要。
PHP SPL模块中自带一个观察者模式
php提供的两个接口,一个被观察者接口SplSubject,一个或多个观察者接口SplObserver,和一个可以储存对象的类SplObjectStorage。
1、SplSubject
被观察者接口SplSubject有三个方法,需要实现这三个方法,一个attach可以理解为添加一个观察者,detach可以理解为删除掉一个观察者,notify里面,循环执行观察者的update方法(被观察者被存储在splobjectstorage类里面),update方法把本类作为参数传进去。
2、SplObserver
观察者接口SplObserver中的update( )方法对于观察者模式至关重要,因为它会得到Subject状态的最新变化,并交给观察者实例。
3、SplObjectStorage
SplObjectStorage类与观察者设计模式没有内在的关系,不过通过它可以很方便地将观察者实例与一个主题实例相关联以及解除关联。
SplObjectStorage对象用来存储观察者对象,这样就省去了底层数组的很多操作细节,比如in_array判断观察者对象是否已经存在了,这是一种委托设计模式。
Demo代码
1 <?php 2 3 // 当用户注册成功时,邮件观察者或短信观察者则收到相应的通知,发送邮件和短信给用户 4 5 // 通知者-用户类 6 class User implements SplSubject 7 { 8 public $name; 9 public $email; 10 public $mobile; 11 // 当前观察者集合 12 private $observers = []; 13 14 public function __construct() 15 { 16 $this->observers = new SplObjectStorage(); 17 } 18 19 // 模拟注册 20 public function register($name, $email, $mobile) 21 { 22 $this->name = $name; 23 $this->email = $email; 24 $this->mobile = $mobile; 25 26 // business handle and register success 27 $reg_result = true; 28 if ($reg_result) { 29 $this->notify(); // 注册成功 所有的观察者将会收到此主题的通知 30 return true; 31 } 32 33 return false; 34 } 35 36 public function attach(SplObserver $observer) 37 { 38 $this->observers->attach($observer); 39 } 40 41 public function detach(SplObserver $observer) 42 { 43 $this->observers->detach($observer); 44 } 45 46 public function notify() 47 { 48 if ($this->observers) { 49 foreach ($this->observers as $observer) { 50 $observer->update($this); 51 } 52 } 53 } 54 } 55 56 class EmailObserver implements SplObserver 57 { 58 public function update(SplSubject $user) 59 { 60 echo "send email to " . $user->email . PHP_EOL; 61 } 62 } 63 64 class SmsObserver implements SplObserver 65 { 66 public function update(SplSubject $user) 67 { 68 echo "send sms to " . $user->mobile . PHP_EOL; 69 } 70 } 71 72 // 业务 73 $user = new User(); 74 $emailObserver = new EmailObserver(); 75 $smsObserver = new SmsObserver(); 76 77 // 添加观察者 78 $user->attach($emailObserver); 79 $user->attach($smsObserver); 80 81 // 删除Sms 观察者 82 $user->detach($smsObserver); 83 84 // 根据注册结果通知观察者,观察者做相应的处理 85 $user->register("小明", "123@qq.com", "18502365895"); 86 87 // 执行结果 88 // send email to 123@qq.com
总结:
观察者模式所做的工作其实就是在解除耦合,让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响另一边的变化。这其实是依赖倒置原则的最佳体现。
参考链接:
https://segmentfault.com/a/1190000010502678