我们在日常生活中有很多观察者模式应用的场景。比如,凡是去过银行营业大厅办理业务的人,大多会有这么一段经历:办理业务之前,先要在营业大厅的门口领取一个排队号,然后你就可以在休息区等待叫号,当轮到自己办理业务的时候,某个柜台上方悬挂的小显示屏就会出现“请XXX号到XX柜台办理业务”。有的时候,某个柜台可能暂时停止办理业务,那么柜台上方的小屏就显示跟其它柜台上方的小屏同样的内容,以便提醒当前用户办理。对于这样的一个需求,你会怎么去实现它呢?我经过一番思考之后,写出了这么一段代码:
Java代码:
//银行柜台类 import java.util.Vector; public class Counter { //小屏列表 private Vector<SmallScreen> vectSmallScreen = new Vector<SmallScreen>(); //当前业务号 private String bizNo; //柜台名称 private String name; //构造函数 public Counter(String name){ this.name = name; } //增加一个小屏 public void attach(SmallScreen smallScreen){ vectSmallScreen.add(smallScreen); } //去除一个小屏 public void detach(SmallScreen smallScreen){ vectSmallScreen.remove(smallScreen); } //通知小屏显示 public void notifyChange(){ for(int i=0; i<vectSmallScreen.size(); i++){ SmallScreen smallScreen = vectSmallScreen.get(i); smallScreen.display(); } } //获取当前业务号 public String getBizNo(){ return this.bizNo; } //设置当前业务号 public void setBizNo(String bizNo){ this.bizNo = bizNo; } //获取主题信息 public String getSubject(){ return "请" + this.bizNo + "号到" + this.name + "号柜台办理业务"; } } //小显示屏类 public class SmallScreen { private String name; private Counter counter; //构造函数 public SmallScreen(String name,Counter counter){ this.name = name; this.counter = counter; } //更新显示屏 public void display(){ try{ System.out.println(this.name + ":" + counter.getSubject()); } catch(Exception err){ } } } //业务系统类 public class BankBiz { public static void main(String[] args) { Counter counter = new Counter("1号柜台"); //1,2号小屏 SmallScreen smallScreen1 = new SmallScreen("1号小屏",counter); SmallScreen smallScreen2 = new SmallScreen("2号小屏",counter); //加入通知小屏 counter.attach(smallScreen1); counter.attach(smallScreen2); //9号办理业务 counter.setBizNo("9"); //通知小屏更新 counter.notifyChange(); } }
Php代码:
<?php //银行柜台类 class Counter { //小屏列表 private $arrSmallScreen = array(); //当前业务号 private $bizNo = ""; //柜台名称 private $name = ""; //构造函数 public function __construct($name){ $this->name = $name; } //增加一个小屏 public function attach(&$smallScreen){ $this->arrSmallScreen[] = $smallScreen; } //去除一个小屏 public function detach(&$smallScreen){ for($i=0; $i<count($this->arrSmallScreen); $i++){ $objTemp = $this->arrSmallScreen[$i]; if($smallScreen === $objTemp){ array_splice($this->arrSmallScreen,$i,1); break; } } } //通知小屏显示 public function notifyChange(){ foreach ($this->arrSmallScreen as $smallScreen){ $smallScreen->display(); } } //获取当前业务号 public function getBizNo(){ return $this->bizNo; } //设置当前业务号 public function setBizNo($bizNo){ $this->bizNo = $bizNo; } //获取主题信息 public function getSubject(){ return "请" + $this->bizNo . "号到" . $this->name . "号柜台办理业务"; } } //小显示屏类 class SmallScreen { private $name = ""; private $counter = null; //构造函数 public function __construct($name,$counter){ $this->name = $name; $this->counter = $counter; } //更新显示屏 public function display(){ echo $this->name . ":" . $this->counter->getSubject(); } } //业务系统类 class BankBiz { public static function execute() { $counter = new Counter("1号柜台"); //1,2号小屏 $smallScreen1 = new SmallScreen("1号小屏",$counter); $smallScreen2 = new SmallScreen("2号小屏",$counter); //加入通知小屏 $counter->attach($smallScreen1); $counter->attach($smallScreen2); $counter->detach($smallScreen2); //9号办理业务 $counter->setBizNo("9"); //通知小屏更新 $counter->notifyChange(); } } BankBiz::execute(); ?>
//运行结果如下
1号小屏:请9号到1号柜台号柜台办理业务
2号小屏:请9号到1号柜台号柜台办理业务
观察者模式又叫做发布-订阅(Publish/Subscribe)模式。观察者模式定义了一种一对多地依赖模式,让多个观察者同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有的观察者对象,使它们能够自动更新自己。这里的主题对象就是指通知者,又叫做发布者。观察者又叫订阅者。 在上面这段代码中,我们让多个小屏同时监听银行柜台信息的变化,每当发生变化时,就立即通知所有监听的小屏,这些小屏就会更新自己的显示内容。在这个案例中,小屏就是观察者,银行柜台就是通知者,当通知者自身发生变化后,就会通知观察者,观察者会根据自身情况进行相应的更新。这就是观察者模式的雏形。如果我们不使用观察者模式,那么用什么办法才能实现这样的需求呢?最直接的想法就是让各个小屏每隔一段时间就查询一次银行柜台,并判断银行柜台是否发生了改变,如果发生了改变,就去更新自己的显示内容,显然这是一种轮询的实现方式。根据这个思路,小屏类SmallScreen的Update方法就应该这样实现:
Java代码:
//更新显示屏 public void display(){ try{ //使用死循环判断主题是否发生改变 while( counter.getSubjectChanged() ){ //中间休息1秒钟 Thread.sleep(1000); //显示改变后的主题 System.out.println(this.name + ":" + counter.getSubject()); } } catch(Exception err){ } }
Php代码:
//更新显示屏 public function display(){ //使用死循环判断主题是否发生改变 while( $this->counter->getSubjectChanged() ){ //中间休息1秒钟 sleep(1000); //显示改变后的主题 echo $this->name . ":" . $this->counter->getSubject()); } }
这跟观察者模式到底有什么不同呢?很显然,这种实现方式是一种“拉模型”,观察者需要自己主动轮询通知者的状态。观察者模式与之相反,它是一种“推模型”,通知者把变化通知给观察者。观察者模式又叫做发布-订阅(Publish/Subscribe)模式。观察者模式定义了一种一对多地依赖模式,让多个观察者同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有的观察者对象,使它们能够自动更新自己。这里的主题对象就是指通知者,又叫做发布者。观察者又叫订阅者。在这个例子中,小屏类SmallScreen就是观察者Observer,银行柜台类Counter就是主题对象Subject。
我们的代码已经解决了由“小屏往回拉信息”到“柜台推送信息”的模式改变,这种改变的好处是效率的得到了提高,至少不用小屏频繁的访问银行柜台了,但是系统的可扩展性和可维护性却变差了,为什么这么说呢?我们可以分析一下:在“小屏往回拉信息”的模式下,银行柜台类Counter,只要提供两个方法就行了,一个是getSubjectChanged,小屏通过它就可以知道银行柜台是否发生变化,另一个是getSubject,小屏通过它就可以获取银行柜台发布的内容,甚至于这两个方法完全可以合并成一个。在这种模式下,Counter与SmallScreen耦合点就是这两个方法。类SmallScreen必须访问类Counter,但是类Counter不必访问类SmallScreen,两者是一个单向耦合的关系。在“柜台推送信息”的模式下,我们可以看一下代码。银行柜台类Counter发生变化的时候,强制小屏类SmallScreen进行Update,所以类Counter必须用到类SmallScreen,这是第一个耦合点。小屏类SmallScreen更新自己内容的时候,需要调用银行柜台类Counter的getSubject方法,这是第二个耦合点。所以,两者互相调用对方的方法,这是一个双向耦合的关系。双向耦合有什么危害呢?耦合越多越紧密,系统就越难以维护,无论是修改或者扩展其中一个类的功能,都可能影响到其它的类。如果类之间都是独立的,或者联系非常弱,那么修改任何一个类,都不会影响到其它类,这是非常理想的状态。现在银行方面又追加了一个需求:除了小屏显示信息之外,还需要通过音箱进行呼叫。这个需求在排队系统中是非常普遍的。我们之前完成的代码怎样应对这种变化呢?首先,我们需要再创建一个音箱类Speaker,然后需要在类Counter里面再增加一个音箱列表,最后在类Counter通知变化的方法notifyChange里再遍历一遍音箱列表。
Java代码:
//银行柜台类 import java.util.Vector; public class Counter { //小屏列表 private Vector<SmallScreen> vectSmallScreen = new Vector<SmallScreen>(); //音箱列表 private Vector<Speaker> vectSpeaker = new Vector<Speaker>(); ...... //通知小屏显示和音箱呼叫 public void notifyChange(){ for(int i=0; i<vectSmallScreen.size(); i++){ SmallScreen smallScreen = vectSmallScreen.get(i); smallScreen.display(); } for(int i=0; i< vectSpeaker.size(); i++){ Speaker speaker = vectSpeaker.get(i); speaker.call(); } } ...... } //音箱类 public class Speaker { private String name; private Counter counter; //构造函数 public Speaker(String name,Counter counter){ this.name = name; this.counter = counter; } //更新音箱呼叫 public void call(){ try{ System.out.println(this.name + ":" + counter.getSubject()); } catch(Exception err){ } } }
Php代码:
//银行柜台类 public class Counter { //小屏列表 private $arrSmallScreen = array(); //音箱列表 private $arrSpeaker = array(); ...... //通知小屏显示和音箱呼叫 public function notifyChange(){ foreach ($this->arrSmallScreen as $smallScreen){ $smallScreen->display(); } foreach ($this->arrSpeaker as $speaker){ $speaker->call(); } } ...... } //音箱类 class Speaker { private $name = ""; private $counter = null; //构造函数 public function __construct($name,$counter){ $this->name = $name; $this->counter = $counter; } //更新显示屏 public function call(){ echo $this->name . ":" . $this->counter->getSubject(); } }
我们增加了一个类Speaker,还修改了类Counter,,当前提出的需求算是解决了,可是代码的耦合度却加大了,已经有3个类纠缠在一起了,那么以后再追加接收信息的终端怎么办?每一次追加观察者,都不得不改一遍类Counter的代码,如果继续做下去,出错的几率将不断加大,可维护性不断降低,这段代码明显违背开闭原则,所以,我们需要重新审视之前的设计了。
面向对象的设计中最重要的思维就是抽象。显然,无论是小屏、大屏还是音箱,它们只是外观和更新信息的手段有所不同,更新信息的功能却是一致的,所以,我们完全可以把这些观察者抽象出来一个基类Observer,更新信息的手段由各个观察者自己负责实现。我们再修改一遍代码:
Java代码:
//银行柜台类 import java.util.Vector; public class Counter { //观察者列表 private Vector<Observer> vectObserver = new Vector<Observer>(); //当前业务号 private String bizNo; //柜台名称 private String name; //构造函数 public Counter(String name){ this.name = name; } //增加一个小屏 public void attach(Observer observer){ vectObserver.add(observer); } //去除一个小屏 public void detach(Observer observer){ vectObserver.remove(observer); } //通知小屏显示 public void notifyChange(){ for(int i=0; i<vectObserver.size(); i++){ Observer observer = vectObserver.get(i); observer.update(); } } //获取当前业务号 public String getBizNo(){ return this.bizNo; } //设置当前业务号 public void setBizNo(String bizNo){ this.bizNo = bizNo; } //获取主题信息 public String getSubject(){ return "请" + this.bizNo + "号到" + this.name + "号柜台办理业务"; } } //通知设备基类 abstract public class Observer { protected String name; protected Counter counter; //构造函数 public Observer(String name,Counter counter){ this.name = name; this.counter = counter; } //更新信息 public abstract void update(); } //小显示屏类 public class SmallScreen extends Observer{ //构造函数 public SmallScreen(String name,Counter counter){ super(name,counter); } //更新显示屏 public void update(){ try{ System.out.println(this.name + ":" + counter.getSubject()); } catch(Exception err){ } } } //音箱类 public class Speaker extends Observer{ //构造函数 public Speaker(String name,Counter counter){ super(name,counter); } //更新音箱呼叫 public void update(){ try{ System.out.println(this.name + ":" + counter.getSubject()); } catch(Exception err){ } } } //业务系统类 public class BankBiz { public static void main(String[] args) { Counter counter = new Counter("1号柜台"); //1,2号小屏 SmallScreen smallScreen1 = new SmallScreen("1号小屏",counter); SmallScreen smallScreen2 = new SmallScreen("2号小屏",counter); //3号音箱 Speaker speaker = new Speaker("3号音箱",counter); //加入通知小屏 counter.attach(smallScreen1); counter.attach(smallScreen2); counter.attach(speaker); //9号办理业务 counter.setBizNo("9"); //通知小屏更新 counter.notifyChange(); } }
Php代码:
<?php //银行柜台类 class Counter { //观察者列表 private $arrSmallScreen = array(); //当前业务号 private $bizNo = ""; //柜台名称 private $name = ""; //构造函数 public function __construct($name){ $this->name = $name; } //增加一个小屏 public function attach(&$smallScreen){ $this->arrSmallScreen[] = $smallScreen; } //去除一个小屏 public function detach(&$smallScreen){ for($i=0; $i<count($this->arrSmallScreen); $i++){ $objTemp = $this->arrSmallScreen[$i]; if($smallScreen === $objTemp){ array_splice($this->arrSmallScreen,$i,1); break; } } } //通知小屏显示 public function notifyChange(){ foreach ($this->arrSmallScreen as $smallScreen){ $smallScreen->update(); } } //获取当前业务号 public function getBizNo(){ return $this->bizNo; } //设置当前业务号 public function setBizNo($bizNo){ $this->bizNo = $bizNo; } //获取主题信息 public function getSubject(){ return "请" + $this->bizNo . "号到" . $this->name . "号柜台办理业务"; } } //通知设备基类 abstract class Observer { protected $name = ""; protected $counter = ""; //构造函数 public function __construct($name,$counter){ $this->name = $name; $this->counter = $counter; } //更新信息 public abstract function update(); } //小显示屏类 class SmallScreen extends Observer{ //构造函数 public function __construct($name,$counter){ parent::__construct($name,$counter); } //更新显示屏 public function update(){ echo $this->name . ":" . $this->counter->getSubject(); } } //音箱类 class Speaker extends Observer{ //构造函数 public function __construct($name,$counter){ parent::__construct($name,$counter); } //更新显示屏 public function update(){ echo $this->name . ":" . $this->counter->getSubject(); } } //业务系统类 class BankBiz { public static function execute() { $counter = new Counter("1号柜台"); //1,2号小屏 $smallScreen1 = new SmallScreen("1号小屏",$counter); $smallScreen2 = new SmallScreen("2号小屏",$counter); //3号音箱 $speaker = new Speaker("3号音箱",$counter); //加入通知小屏 $counter->attach($smallScreen1); $counter->attach($smallScreen2); $counter->attach($speaker); //9号办理业务 $counter->setBizNo("9"); //通知小屏更新 $counter->notifyChange(); } } BankBiz::execute(); ?>
//运行结果如下
1号小屏:请9号到1号柜台号柜台办理业务
2号小屏:请9号到1号柜台号柜台办理业务
3号音箱:请9号到1号柜台号柜台办理业务
经过我们这么一番改造,应对观察者的加入是绝对没有问题了,再也不用去修改柜台类Counter了,把柜台类对具体终端的依赖关系给去除了。不管是小屏还是音箱,柜台类一视同仁,进行同样的处理,它根本不需要知道需要通知的对象是什么。比如:银行又提出来加入大屏的显示。这样的需求,对于我们来说已经是很easy的事情了,可以从Observer类再派成出来一个大屏类LargeScreen。有人问:“大屏和小屏还不一样,为什么不用一个类来表示呢”。其实,做过排队项目的人可能很清楚,大屏幕的显示驱动和显示方式可能与小屏完全不同,甚至于供货厂商都不一样,在这个案例中,我们只是剥离出来它的其中一项显示功能而已。又比如:银行为了提升服务质量,准备加入短信提醒用户的功能。现在,我们是不是很容易对付了?
我们在开发软件的时候,用户需求很难做到百分之百的稳定,只能做到尽量稳定。银行又提出了新的需求:“除了柜台上可以发布呼号信息以外,银行内部的管理部门在某些情况下,也能利用大小屏发布一些紧急的信息”。 我们按照处理观察者方法,可以把这些通知者抽象出来,形成一个基类Subject,银行柜台和管理部门作为两个通知者,都可以从基类Subject派生出来,以后再增加通知者,就能够以此类推。我们按照这个思路形成最终一个版本:
Java代码:
//主题基类 import java.util.Vector; public abstract class Subject { //观察者列表 private Vector<Observer> vectObserver = new Vector<Observer>(); //增加一个观察者 public void attach(Observer observer){ vectObserver.add(observer); } //去除一个观察者 public void detach(Observer observer){ vectObserver.remove(observer); } //通知观察者更新 public void notifyObservers(){ for(int i=0; i<vectObserver.size(); i++){ Observer observer = vectObserver.get(i); observer.update(); } } //获取主题信息 public abstract String getSubject(); } //银行柜台类 public class Counter extends Subject{ //当前业务号 private String bizNo; //柜台名称 private String name; //构造函数 public Counter(String name){ this.name = name; } //获取当前业务号 public String getBizNo(){ return this.bizNo; } //设置当前业务号 public void setBizNo(String bizNo){ this.bizNo = bizNo; } //获取主题信息 public String getSubject(){ return "请" + this.bizNo + "号到" + this.name + "号柜台办理业务"; } } //管理部门类 public class Manager extends Subject{ //管理部门名称 private String name; //构造函数 public Manager(String name){ this.name = name; } //获取主题信息 public String getSubject(){ return this.name + "发布最新紧急公告"; } } //观察者基类 public abstract class Observer { protected String name; protected Subject subject; //构造函数 public Observer(String name,Subject subject){ this.name = name; this.subject = subject; } //更新信息 public abstract void update(); } //小显示屏类 public class SmallScreen extends Observer{ //构造函数 public SmallScreen(String name,Subject subject){ super(name,subject); } //更新显示屏 public void update(){ try{ System.out.println(this.name + ":" + subject.getSubject()); } catch(Exception err){ } } } //音箱类 public class Speaker extends Observer{ //构造函数 public Speaker(String name,Subject subject){ super(name,subject); } //更新音箱 public void update(){ try{ System.out.println(this.name + ":" + subject.getSubject()); } catch(Exception err){ } } } //业务系统类 public class BankBiz { public static void main(String[] args) { //银行柜台 Counter counter = new Counter("1号柜台"); //1,2号小屏、3号音箱 SmallScreen smallScreen1 = new SmallScreen("1号小屏",counter); SmallScreen smallScreen2 = new SmallScreen("2号小屏",counter); Speaker speaker = new Speaker("3号音箱",counter); //银行柜台加入观察者 counter.attach(smallScreen1); counter.attach(smallScreen2); counter.attach(speaker); //9号办理业务 counter.setBizNo("9"); //通知更新 counter.notifyObservers(); //管理部门 Manager manager = new Manager("风险控制部"); //1号小屏 smallScreen1 = new SmallScreen("1号小屏",manager); //管理部门加入观察者 manager.attach(smallScreen1); //通知更新 manager.notifyObservers(); } }
php代码:
<?php //主题基类 abstract class Subject { //观察者列表 private $arrObserver = array(); //增加一个观察者 public function attach(&$observer){ $this->arrObserver[] = $observer; } //去除一个观察者 public function detach(&$observer){ for($i=0; $i<count($this->$arrObserver); $i++){ $objTemp = $this->$arrObserver[$i]; if($observer === $objTemp){ array_splice($this->$arrObserver,$i,1); break; } } } //通知观察者更新 public function notifyObservers(){ foreach ($this->arrObserver as $observer){ $observer->update(); } } //获取主题信息 public abstract function getSubject(); } //银行柜台类 class Counter extends Subject { //当前业务号 private $bizNo = ""; //柜台名称 private $name = ""; //构造函数 public function __construct($name){ $this->name = $name; } //获取当前业务号 public function getBizNo(){ return $this->bizNo; } //设置当前业务号 public function setBizNo($bizNo){ $this->bizNo = $bizNo; } //获取主题信息 public function getSubject(){ return "请" + $this->bizNo . "号到" . $this->name . "号柜台办理业务"; } } //管理部门类 class Manager extends Subject{ //管理部门名称 private $name = ""; //构造函数 public function __construct($name){ $this->name = $name; } //获取主题信息 public function getSubject(){ return $this->name . "发布最新紧急公告"; } } //观察者基类 abstract class Observer { protected $name = ""; protected $subject = ""; //构造函数 public function __construct($name,$subject){ $this->name = $name; $this->subject = $subject; } //更新信息 public abstract function update(); } //小显示屏类 class SmallScreen extends Observer{ //构造函数 public function __construct($name,$subject ){ parent::__construct($name,$subject); } //更新显示屏 public function update(){ echo $this->name . ":" . $this->subject->getSubject(); } } //音箱类 class Speaker extends Observer{ //构造函数 public function __construct($name,$subject){ parent::__construct($name,$subject); } //更新音响 public function update(){ echo $this->name . ":" . $this->subject->getSubject(); } } //业务系统类 class BankBiz { public static function execute() { //银行柜台 $counter = new Counter("1号柜台"); //1,2号小屏、3号音箱 $smallScreen1 = new SmallScreen("1号小屏",$counter); $smallScreen2 = new SmallScreen("2号小屏",$counter); $speaker = new Speaker("3号音箱",$counter); //银行柜台加入观察者 $counter->attach($smallScreen1); $counter->attach($smallScreen2); $counter->attach($speaker); //9号办理业务 $counter->setBizNo("9"); //通知更新 $counter->notifyObservers(); //管理部门 $manager = new Manager("风险控制部"); //1号小屏 $smallScreen1 = new SmallScreen("1号小屏",$manager); //管理部门加入观察者 $manager->attach($smallScreen1); //通知更新 $manager->notifyObservers(); } } BankBiz::execute(); ?>
又经过我们的一番改造,应对主题和观察者的需求变化都没有什么问题了,以后针对类似这样的需求,我们已经非常有经验了,完全可以把这段代码稍加修改套用一下就可以了。换句话说,我们把观察者模式的实现方式做成了一个很小的框架。
转载于:https://www.cnblogs.com/hengbo/archive/2009/05/14/2232494.html
相关资源:数据结构—成绩单生成器