14.4解耦实践二

代码结构图 第十四章 老板回来,我不知道——观察者模式 - 图1代码实现
抽象通知者接口

  1. //通知者接口
  2. interface Subject
  3. {
  4. public function Attach($observer);
  5. public function Detach($observer);
  6. public function Notify();
  7. public function SubjectState();
  8. }

具体通知类

  1. class Boss implements Subject
  2. {
  3. private $observers = array();
  4. private $action = '';
  5. public function Attach($observer)
  6. {
  7. $this->observers[] = $observer;
  8. }
  9. public function Detach($observer)
  10. {
  11. foreach ($this->observers as $key => $value) {
  12. if ($value == $observer) {
  13. unset($this->observers[$key]);
  14. }
  15. }
  16. }
  17. public function Notify()
  18. {
  19. foreach ($this->observers as $value) {
  20. $value->Update();
  21. }
  22. }
  23. public function setSubjectState($action)
  24. {
  25. $this->action = $action;
  26. }
  27. public function SubjectState()
  28. {
  29. return $this->action;
  30. }
  31. }

观察者抽象类

  1. abstract class Observer
  2. {
  3. protected $name = '';
  4. protected $sub = null;
  5. public function __construct($name, $sub)
  6. {
  7. $this->name = $name;
  8. $this->sub = $sub;
  9. }
  10. abstract function Update();
  11. }

具体观察者类

  1. class StockObserver extends Observer
  2. {
  3. public function __construct($name, $sub)
  4. {
  5. parent::__construct($name, $sub);
  6. }
  7. public function Update()
  8. {
  9. echo $this->sub->SubjectState() . $this->name . '关闭股票行情,继续工作!' . PHP_EOL;
  10. }
  11. }
  12. class NBAObserver extends Observer
  13. {
  14. public function __construct($name, $sub)
  15. {
  16. parent::__construct($name, $sub);
  17. }
  18. public function Update()
  19. {
  20. echo $this->sub->SubjectState() . $this->name . '关闭NBA直播,继续工作!' . PHP_EOL;
  21. }
  22. }

客户端代码

  1. public function observerImp()
  2. {
  3. $boss = new Boss();
  4. $colA = new StockObserver('小明', $boss);
  5. $colB = new NBAObserver('小红', $boss);
  6. $boss->Attach($colA);
  7. $boss->Attach($colB);
  8. $boss->Detach($colB);
  9. $boss->setSubjectState('我胡汉三又回来了 ');
  10. $boss->Notify();
  11. }

14.5观察者模式

观察者模式,又叫做发布-订阅(Publish/Subscribe)模式,定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。
观察者模式(Observer)结构图 第十四章 老板回来,我不知道——观察者模式 - 图2代码示例
Subject类,可翻译为主题或抽象通知者,一般用一个抽象类或者一个接口实现。它把所有对观察者对象的引用保存在一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。

  1. abstract class Subject
  2. {
  3. private $observers = array();
  4. //增加观察者
  5. public function Attach($observer)
  6. {
  7. $this->observers[] = $observer;
  8. }
  9. //移除观察者
  10. public function Detach($observer)
  11. {
  12. foreach ($this->observers as $key => $value) {
  13. if ($value == $observer) {
  14. unset($this->observers[$key]);
  15. }
  16. }
  17. }
  18. //通知
  19. public function Notify()
  20. {
  21. foreach ($this->observers as $value) {
  22. $value->Update();
  23. }
  24. }
  25. }

Observer类,抽象观察者,为所有的具体观察者定义一个接口,在得到主题的通知时更新自己。这个接口叫做更新接口。抽象观察者一般用一个抽象类或者一个接口实现。更新接口通常包含一个Update()方法,这个方法叫做更新方法。

  1. abstract class Observer
  2. {
  3. abstract function Update();
  4. }

ConcreteSubject类,叫做具体主题或具体通知者,将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色通常用一个具体子类实现。

  1. class ConcreteSubject extends Subject
  2. {
  3. private $subjectState = '';
  4. public function setSubjectState($state)
  5. {
  6. $this->subjectState = $state;
  7. }
  8. //具体被观察者状态
  9. public function SubjectState()
  10. {
  11. return $this->subjectState;
  12. }
  13. }

Concrete Observer类,具体观察者,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。具体观察者角色可以保存一个指向具体主题对象的引用。具体观察者角色通常用一个具体子类实现。

  1. class ConcreteObserver extends Observer
  2. {
  3. private $name = '';
  4. private $subject = null;
  5. public function __construct($subject, $name)
  6. {
  7. $this->subject = $subject;
  8. $this->name = $name;
  9. }
  10. public function Update()
  11. {
  12. echo '观察者' . $this->name . '的新状态是' . $this->subject->SubjectState() . PHP_EOL;
  13. }
  14. }

客户端代码

  1. public function observerDemo()
  2. {
  3. $s = new ConcreteSubject();
  4. $s->Attach(new ConcreteObserver($s, 'X'));
  5. $s->Attach(new ConcreteObserver($s, 'Y'));
  6. $s->Attach(new ConcreteObserver($s, 'Z'));
  7. $s->setSubjectState('ABC');
  8. $s->Notify();
  9. }

14.6观察者模式特点

观察者模式的使用动机:
将一个系统分割成一系列相互协作的类有一个很不好的副作用,那就是需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不便。而观察者模式的关键对象是主题Subject和观察者Observer,一个Subject可以有任意数目的依赖它的Observer,一旦Subject的状态发生了改变,所有的Observer都可以得到通知。Subject发出通知时并不需要知道谁是它的观察者,也就是说,具体观察者是谁,它根本不需要知道。而任何一个具体观察者不知道也不需要知道其他观察者的存在。
观察者模式的使用场景:

  • 当一个对象的改变需要同时改变其他对象的时候,而且它不知道具体有多少对象有待改变时,应该考虑使用观察者模式。
  • 当一个抽象模型有两个方面,其中一个方面依赖于另一方面,这时用观察者模式可以将这两者封装在独立的对象中使它们各自独立地改变和复用。

总的来讲,观察者模式所做的工作其实就是在解除耦合。让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响另一边的变化。(依赖倒转原则的最佳体现)

14.7观察者模式的不足

尽管已经用了依赖倒转原则,但是‘抽象通知者’还是依赖‘抽象观察者’,也就是说,万一没有了抽象观察者这样的接口,我这通知的功能就完不成了。另外就是每个具体观察者,它不一定是‘更新’的方法要调用,也可能是根本不同名的方法。这应该就是不足的地方。
如果通知者和观察者之间根本就互相不知道,由客户端来决定通知谁,那就好了。

14.8事件委托实现

代码实现
具体观察者类,去掉了父类“抽象观察类”,并将“更新方法名改为各自适合的方法名。

  1. //看股票的同事
  2. class StockObserver
  3. {
  4. private $name = '';
  5. private $sub = null;
  6. public function __construct($name, $sub)
  7. {
  8. $this->name = $name;
  9. $this->sub = $sub;
  10. }
  11. //关闭股票行情
  12. public function CloseStockMarket()
  13. {
  14. echo $this->sub->SubjectState() . $this->name . '关闭股票行情,继续工作!' . PHP_EOL;
  15. }
  16. }
  17. //看NBA的同事
  18. class NBAObserver
  19. {
  20. private $name = '';
  21. private $sub = null;
  22. public function __construct($name, $sub)
  23. {
  24. $this->name = $name;
  25. $this->sub = $sub;
  26. }
  27. //关闭NBA直播
  28. public function CloseNBADirectSeeding()
  29. {
  30. echo $this->sub->SubjectState() . $this->name . '关闭NBA直播,继续工作!' . PHP_EOL;
  31. }
  32. }

“抽象通知者”由于不希望依赖“抽象观察者”,所以“增加”和“减少”的方法也就没有必要了(抽象观察者已经不存在了)。

  1. interface Subject
  2. {
  3. public function Notify();
  4. public function SubjectState();
  5. }

具体通知者类

  1. class Boss implements Subject
  2. {
  3. private $action = '';
  4. private $func_list = array();
  5. public function Update($func_list)
  6. {
  7. $this->func_list[] = $func_list;
  8. }
  9. public function Notify()
  10. {
  11. foreach ($this->func_list as $value) {
  12. call_user_func_array([$value[0], $value[1]], $value[2]);
  13. }
  14. }
  15. public function setSubjectState($action)
  16. {
  17. $this->action = $action;
  18. }
  19. public function SubjectState()
  20. {
  21. return $this->action;
  22. }
  23. }
  24. class Secretary implements Subject
  25. {
  26. //与老板类类似,省略
  27. }

客户端代码

  1. public function observerImpDeep()
  2. {
  3. $boss = new BossDeep();
  4. $colA = new StockObserverDeep('小明', $boss);
  5. $colB = new NBAObserverDeep('小红', $boss);
  6. $boss->Update(array($colA, 'CloseStockMarket', []));
  7. $boss->Update(array($colB, 'CloseNBADirectSeeding', []));
  8. $boss->setSubjectState('我胡汉三又回来了 ');
  9. $boss->Notify();
  10. }

显示结果

  1. 我胡汉三又回来了 小明关闭股票行情,继续工作!
  2. 我胡汉三又回来了 小红关闭NBA直播,继续工作!

14.9事件委托说明

委托就是一种引用方法的类型。一旦为委托分配了方法,委托将与该方法具有完全相同的行为。委托方法的使用可以像其他任何方法一样,具有参数和返回值。委托可以看作是对函数的抽象,是函数的‘类’,委托的实例将代表一个具体的函数。
一旦为委托分配了方法,委托将与该方法具有完全相同的行为。而且,一个委托可以搭载多个方法,所有方法被依次唤起。更重要的是,它可以使得委托对象所搭载的方法并不需要属于同一个类。
委托对象所搭载的所有方法必须具有相同的原形和形式,也就是拥有相同的参数列表和返回值类型。
注意,是先有观察者模式,再有委托事件技术的,再说,它们各有优缺点,你不妨去看看MSDN,讲的已经很详细了。