观察者模式有时也被称作发布/订阅模式 ,该模式用于为对象实现发布/订阅功能:一旦主体对象 状态发生改变,与之关联的观察者对象 会收到通知,并进行相应操作。
将一个系统分割成一个一些类相互协作的类有一个不好的副作用,那就是需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不便。观察者就是解决这类的耦合关系的。
消息队列系统、事件 都使用了观察者模式。
PHP 为观察者模式定义了两个接口:SplSubject 和 SplObserver。SplSubject 可以看做主体对象 的抽象,SplObserver 可以看做观察者对象 的抽象,要实现观察者模式,只需让主体对象实现 SplSubject ,观察者对象实现 SplObserver ,并实现相应方法即可。
示例1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 interface IObserver { function onChanged ( $sender , $args ) ; } interface IObservable { function addObserver ( $observer ) ; } class UserList implements IObservable { private $_observers = array (); public function addCustomer ( $name ) { foreach ( $this ->_observers as $obs ) $obs ->onChanged ( $this , $name ); } public function addObserver ( $observer ) { $this ->_observers []= $observer ; } } class UserListLogger implements IObserver { public function onChanged ( $sender , $args ) { echo ( "'$args ' added to user list\n" ); } } $ul = new UserList ();$ul ->addObserver ( new UserListLogger ());$ul ->addCustomer ( "Jack" );
示例2 使用了php中的 SplSubject,SplObserver 主体对象和观察者对象类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 <?php namespace DesignPatterns \Behavioral \Observer ;class User implements \SplSubject { protected $data = array (); protected $observers ; public function __construct ( ) { $this ->observers = new \SplObjectStorage (); } public function attach (\SplObserver $observer ) { $this ->observers->attach ($observer ); } public function detach (\SplObserver $observer ) { $this ->observers->detach ($observer ); } public function notify ( ) { foreach ($this ->observers as $observer ) { $observer ->update ($this ); } } public function __set ($name , $value ) { $this ->data[$name ] = $value ; $this ->notify (); } } namespace DesignPatterns \Behavioral \Observer ;class UserObserver implements \SplObserver { public function update (\SplSubject $subject ) { echo get_class ($subject ) . ' has been updated' ; } } namespace DesignPatterns \Behavioral \Observer \Tests ;use DesignPatterns \Behavioral \Observer \UserObserver ;use DesignPatterns \Behavioral \Observer \User ;class ObserverTest extends \PHPUnit_Framework_TestCase { protected $observer ; protected function setUp ( ) { $this ->observer = new UserObserver (); } public function testNotify ( ) { $this ->expectOutputString ('DesignPatterns\Behavioral\Observer\User has been updated' ); $subject = new User (); $subject ->attach ($this ->observer); $subject ->property = 123 ; } public function testAttachDetach ( ) { $subject = new User (); $reflection = new \ReflectionProperty ($subject , 'observers' ); $reflection ->setAccessible (true ); $observers = $reflection ->getValue ($subject ); $this ->assertInstanceOf ('SplObjectStorage' , $observers ); $this ->assertFalse ($observers ->contains ($this ->observer)); $subject ->attach ($this ->observer); $this ->assertTrue ($observers ->contains ($this ->observer)); $subject ->detach ($this ->observer); $this ->assertFalse ($observers ->contains ($this ->observer)); } public function testUpdateCalling ( ) { $subject = new User (); $observer = $this ->getMock ('SplObserver' ); $subject ->attach ($observer ); $observer ->expects ($this ->once ()) ->method ('update' ) ->with ($subject ); $subject ->notify (); } }
官方demo 使用了SplObjectStorage 这个对象,这个对象本身有attach ,detach 的方法,如果不用,可以参考第二个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 class MyObserver1 implements SplObserver { public function update (SplSubject $subject ) { echo __CLASS__ . ' - ' . $subject ->getName (); } } class MyObserver2 implements SplObserver { public function update (SplSubject $subject ) { echo __CLASS__ . ' - ' . $subject ->getName (); } } class MySubject implements SplSubject { private $_observers ; private $_name ; public function __construct ($name ) { $this ->_observers = new SplObjectStorage (); $this ->_name = $name ; } public function attach (SplObserver $observer ) { $this ->_observers->attach ($observer ); } public function detach (SplObserver $observer ) { $this ->_observers->detach ($observer ); } public function notify ( ) { foreach ($this ->_observers as $observer ) { $observer ->update ($this ); } } public function getName ( ) { return $this ->_name; } } $observer1 = new MyObserver1 ();$observer2 = new MyObserver2 ();$subject = new MySubject ("test" );$subject ->attach ($observer1 );$subject ->attach ($observer2 );$subject ->notify ();
一个报纸和订阅者的例子,很像发布和订阅 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 class Newspaper implements \SplSubject { private $name ; private $observers = array (); private $content ; public function __construct ($name ) { $this ->name = $name ; } public function attach (\SplObserver $observer ) { $this ->observers[] = $observer ; } public function detach (\SplObserver $observer ) { $key = array_search ($observer ,$this ->observers, true ); if ($key ){ unset ($this ->observers[$key ]); } } public function breakOutNews ($content ) { $this ->content = $content ; $this ->notify (); } public function getContent ( ) { return $this ->content." ({$this->name} )" ; } public function notify ( ) { foreach ($this ->observers as $value ) { $value ->update ($this ); } } } class Reader implements SplObserver { private $name ; public function __construct ($name ) { $this ->name = $name ; } public function update (\SplSubject $subject ) { echo $this ->name.' is reading breakout news <b>' .$subject ->getContent ().'</b><br>' ; } } $newspaper = new Newspaper ('Newyork Times' );$allen = new Reader ('Allen' );$jim = new Reader ('Jim' );$linda = new Reader ('Linda' );$newspaper ->attach ($allen );$newspaper ->attach ($jim );$newspaper ->attach ($linda );$newspaper ->detach ($linda );$newspaper ->breakOutNews ('USA break down!' );