前言
觀察者(Observer)模式是一種使用頻率很高的設(shè)計(jì)模式,之前在介紹Javax.Servlet包下的Listener的時(shí)候,我們知道Listener能夠在某一特定事件發(fā)生的時(shí)候(比如Servlet容器的創(chuàng)建和銷毀)作出響應(yīng)。為了完成這個(gè)功能,Listener就采用了觀察者模式。
場(chǎng)景
假設(shè)我們現(xiàn)在有一個(gè)鬧鐘類Clock,當(dāng)把它開(kāi)啟的時(shí)候它就會(huì)在一個(gè)特定的時(shí)間鬧鈴,如果類Son聽(tīng)到這個(gè)鬧鐘的鈴聲,就會(huì)起床去上學(xué)。為了模擬這個(gè)場(chǎng)景,我們可以簡(jiǎn)單的寫下如下代碼。
-
Clock和Son類
class Clock {
private volatile boolean ring;
public void turnOn() {
System.out.println("鬧鐘響了");
ring = true;
}
public boolean isRing(){
return ring;
}
}
class Son {
public void goToSchool(){
System.out.println("孩子起床上學(xué)");
}
}
- 測(cè)試類
public class ObserverTest {
public static Clock clock = new Clock();
public static void main(String[] args) {
Son son = new Son();
new Thread(()->{
try{
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
clock.turnOn();
}).start();
while (!clock.isRing()) {
// wait
}
son.goToSchool();
}
}
- 實(shí)驗(yàn)結(jié)果
鬧鐘響了
孩子起床上學(xué)
Process finished with exit code 0
這個(gè)實(shí)現(xiàn)方式很簡(jiǎn)單,Son一直輪詢判斷鬧鐘有沒(méi)有響就可以了。缺點(diǎn)也很顯然,淪陷的時(shí)候一直在空轉(zhuǎn),占用cpu時(shí)間,造成了資源浪費(fèi)。那有什么辦法可以不用空轉(zhuǎn)去輪詢狀態(tài)呢?我們可以讓鬧鐘鬧鈴的時(shí)候直接通知Son起來(lái)處理就好了,也就是讓son監(jiān)聽(tīng)clock轉(zhuǎn)換為clock通知son。
- 優(yōu)化-取消輪詢
class Clock {
private volatile boolean ring;
private Son son = new Son();
public void turnOn() {
System.out.println("鬧鐘響了");
ring = true;
son.goToSchool();
}
public boolean isRing(){
return ring;
}
}
現(xiàn)在我們把son捆綁在clock上,當(dāng)clock的turn on方法一調(diào)用就立刻調(diào)用son的goToSchool()方法。現(xiàn)在可以說(shuō)我們把son注冊(cè)進(jìn)了clock,使其成為clock的觀察者。觀察者模式到這里就結(jié)束了嗎? 顯然沒(méi)有,考慮到我們對(duì)于一個(gè)事件并不是只有一個(gè)觀察者,假設(shè)在這個(gè)場(chǎng)景下,鬧鐘一響,不僅兒子要起床上學(xué),父親也要起床上班,母親則需要出門買菜,也就是說(shuō)對(duì)于鬧鐘鬧鈴這個(gè)事件,有3個(gè)觀察者,則上述代碼要改成如下。
- 添加的
Father,Mather類
class Father {
public void goToWork(){
System.out.println("爸爸起床上班");
}
}
class Mother {
public void goBuyFood(){
System.out.println("媽媽出門買菜");
}
}
class Son {
public void goToSchool(){
System.out.println("孩子起床上學(xué)");
}
}
-
Clock類
class Clock {
private volatile boolean ring;
private Son son = new Son();
private Father father = new Father();
private Mother mother = new Mother();
public void turnOn() {
System.out.println("鬧鐘響了");
ring = true;
son.goToSchool();
father.goToWork();
mother.goBuyFood();
}
public boolean isRing(){
return ring;
}
}
添加了Father、Mother類之后,我們還要把它們寫死進(jìn)Clock代碼里,此外還要明確每個(gè)觀察者需要被執(zhí)行的方法。所有的觀察者和Clock這個(gè)被觀察對(duì)象緊緊的耦合在一起了。代碼的擴(kuò)展性特別差。何如修改才能讓其真正成為觀察者模式呢?我們知道約定優(yōu)于配置,既然我們不知道每一個(gè)觀察者需要被調(diào)用的具體方法,那我們就約定一個(gè)方法叫做doSomething(),所有的觀察者只要實(shí)現(xiàn)某個(gè)接口實(shí)現(xiàn)該方法就可以了,此外有了這個(gè)接口,我們可以將所有的觀察者對(duì)象看成一類,在被觀察對(duì)象類中提供一個(gè)注冊(cè)接口就可以了。讓我們按照這個(gè)思路來(lái)進(jìn)行優(yōu)化。
- 約定
Observer接口
interface Observer {
public void doSomething();
}
-
Son、Mother、Father類都來(lái)實(shí)現(xiàn)這個(gè)接口
class Father implements Observer{
public void goToWork(){
System.out.println("爸爸起床上班");
}
@Override
public void doSomething() {
this.goToWork();
}
}
class Mother implements Observer {
public void goBuyFood(){
System.out.println("媽媽出門買菜");
}
@Override
public void doSomething() {
goBuyFood();
}
}
class Son implements Observer{
public void goToSchool(){
System.out.println("孩子起床上學(xué)");
}
@Override
public void doSomething() {
}
}
- 修改
Clock類提供添加觀察者方法
class Clock {
private volatile boolean ring;
private List<Observer> observers = new ArrayList<>();
public void addObserver(Observer observer){
observers.add(observer);
}
public void turnOn() {
System.out.println("鬧鐘響了");
ring = true;
for (Observer observer : observers) {
observer.doSomething();
}
}
public boolean isRing(){
return ring;
}
}
- 進(jìn)行測(cè)試
public class ObserverTest {
public static Clock clock = new Clock();
public static void main(String[] args) {
clock.addObserver(new Son());
clock.addObserver(new Father());
clock.addObserver(new Mother());
clock.turnOn();
}
}
- 輸出結(jié)果
鬧鐘響了
爸爸起床上班
媽媽出門買菜