觀察者設計模式
觀察者模式也稱作發佈訂閱模式,監聽器模式,被觀察管理各個觀察者,當被觀察者的狀態有變更的時候,會主動通知觀察者。
通常的情況下,我們會怎麼實現如果一個對象的狀態變更,通知到對相應狀態感興趣的類呢,這個可以分爲主動通知和被動通知。
主動通知:當實體的狀態有變更,然後主動的通知到觀察者我的狀態變更了,觀察者根據相應的狀態實現自己的操作。
被動通知:實體狀態變更,不主動通知,然後觀察者定時的去掃描目標狀態,這種操作比較耗費資源,並且不能做到實時,所以一般都不採用這種方式。
模式結構
具體觀察者模式是如何實現的呢?
首先會有一個目標對象Target,這個是被觀察者主體,它持有一個觀察者列表,觀察者均會實現觀察者接口中的通知方法,以便接收通知。當觀察者狀態有變更時,會循環持有的觀察者列表,然後調用其通知方法。
現在我們模擬一個場景,來用觀察者模式來實現,場景是家裏面有一套智能家庭影院,當我們打開觀影模式時,此時家裏窗簾自動關閉,屋內的燈光自動調整爲昏暗色,然後音響調整爲環繞聲。當家庭影院從觀影模式調到正常模式時,窗簾拉開,屋內燈光打開,音響調整到立體聲。
實現
手工實現
首先我們有一個家庭影院的主體,然後有一些周邊的設備,智能窗簾,智能音響和燈光。代碼實現如下:
package design.observer;
/**
* Created by wangtengfei1 on 2017/9/13.
* 家庭影院的模式,分別是放映模式和正常模式,實際上也就是事件
*/
public enum EventMode {
VIDEO,NORMAL
}
package design.observer;
import java.util.ArrayList;
import java.util.List;
/**
* Created by wangtengfei1 on 2017/9/13.
* 家庭影院主體
*/
public class HomeVideo {
EventMode eventMode;//觀影模式
//註冊的觀察者列表
List<HomeVideoObserver> observerLists = new ArrayList<HomeVideoObserver>();
//增加觀察者
public void addObserver(HomeVideoObserver observer){
observerLists.add(observer);
}
//刪除觀察者
public void deleteObserver(HomeVideoObserver observer){
observerLists.remove(observer);
}
//通知觀察者
public void notifyObserver(EventMode eventMode){
for(HomeVideoObserver observer:observerLists){
observer.update(this.eventMode);
}
}
//家庭影院開關
public void turn(EventMode eventMode){
this.eventMode = eventMode;
notifyObserver(eventMode);
}
}
package design.observer;
/**
* Created by wangtengfei1 on 2017/9/13.
*/
public interface HomeVideoObserver {
public void update(EventMode eventMode);
}
package design.observer;
/**
* Created by wangtengfei1 on 2017/9/13.
* 智能音響
*/
public class Audio implements HomeVideoObserver {
@Override
public void update(EventMode eventMode) {
if(eventMode==EventMode.NORMAL){
System.out.println("\t>>>立體聲模式播放");
}
if (eventMode ==EventMode.VIDEO){
System.out.println("\t>>>環繞聲模式播放");
}
}
}
package design.observer;
/**
* Created by wangtengfei1 on 2017/9/13.
* 智能燈泡
*/
public class Bulb implements HomeVideoObserver {
@Override
public void update(EventMode eventMode) {
if(eventMode==EventMode.NORMAL){
System.out.println("\t>>>燈光調亮");
}
if (eventMode ==EventMode.VIDEO){
System.out.println("\t>>>燈光調暗");
}
}
}
package design.observer;
/**
* Created by wangtengfei1 on 2017/9/13.
* 智能窗簾
*/
public class WindowCurtains implements HomeVideoObserver {
@Override
public void update(EventMode eventMode) {
if(eventMode==EventMode.NORMAL){
System.out.println("\t>>>窗簾開啓");
}
if (eventMode ==EventMode.VIDEO){
System.out.println("\t>>>窗簾關閉");
}
}
}
遙控器,也就是客戶端
package design.observer;
/**
* Created by wangtengfei1 on 2017/9/13.
* 遙控器
*/
public class RemoteControl {
private static final HomeVideo homeVideo = new HomeVideo();
public static void main(String[] args) throws Exception {
HomeVideoObserver bulb = new Bulb();
HomeVideoObserver windowCurtains = new WindowCurtains();
HomeVideoObserver audio = new Audio();
homeVideo.addObserver(bulb);
homeVideo.addObserver(windowCurtains);
homeVideo.addObserver(audio);
System.out.println(">>遙控器按下播放按鈕");
homeVideo.turn(EventMode.VIDEO);
Thread.sleep(100);
System.out.println(">>100後影片播放完畢");
homeVideo.turn(EventMode.NORMAL);
}
}
輸出如下
JDK實現
由於觀察者模式太常用了,所以jdk在工具類中實現了一個觀察者,只需要我們實現Observer接口,並且實現其update方法即可。現在用jdk自帶的觀察者模式改造一下下面的程序。
package design.jdkObserver;
import design.observer.EventMode;
import design.observer.HomeVideoObserver;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
/**
* Created by wangtengfei1 on 2017/9/13.
*/
public class HomeVideo extends Observable {
public EventMode eventMode;//觀影模式
//家庭影院開關
public void turn(EventMode eventMode){
this.eventMode = eventMode;
this.setChanged();
super.notifyObservers();
}
}
package design.jdkObserver;
import design.observer.EventMode;
import java.util.Observable;
import java.util.Observer;
/**
* Created by wangtengfei1 on 2017/9/13.
* 智能音響
*/
public class Audio implements Observer {
public Audio() {
}
public Audio(Observable observable) {
observable.addObserver(this);
}
@Override
public void update(Observable o, Object arg) {
if (o instanceof HomeVideo) {
HomeVideo video = (HomeVideo) o;
if (video.eventMode == EventMode.NORMAL) {
System.out.println("\t>>>立體聲模式播放");
}
if (video.eventMode == EventMode.VIDEO) {
System.out.println("\t>>>環繞聲模式播放");
}
}
}
}
package design.jdkObserver;
import design.observer.EventMode;
import design.observer.HomeVideoObserver;
import java.util.Observable;
import java.util.Observer;
/**
* Created by wangtengfei1 on 2017/9/13.
* 智能燈泡
*/
public class Bulb implements Observer {
public Bulb(){}
public Bulb(Observable observable){
observable.addObserver(this);
}
@Override
public void update(Observable o, Object arg) {
if (o instanceof HomeVideo) {
HomeVideo video = (HomeVideo) o;
if(video.eventMode==EventMode.NORMAL){
System.out.println("\t>>>燈光調亮");
}
if (video.eventMode ==EventMode.VIDEO){
System.out.println("\t>>>燈光調暗");
}
}
}
}
package design.jdkObserver;
import design.observer.EventMode;
import design.observer.HomeVideoObserver;
import java.util.Observable;
import java.util.Observer;
/**
* Created by wangtengfei1 on 2017/9/13.
* 智能窗簾
*/
public class WindowCurtains implements Observer {
public WindowCurtains(){}
public WindowCurtains(Observable observable){
observable.addObserver(this);
}
@Override
public void update(Observable o, Object arg) {
if (o instanceof HomeVideo) {
HomeVideo video = (HomeVideo) o;
if(video.eventMode==EventMode.NORMAL){
System.out.println("\t>>>窗簾開啓");
}
if (video.eventMode ==EventMode.VIDEO){
System.out.println("\t>>>窗簾關閉");
}
}
}
}
jdk方式的觀察者模式的遙控器,客戶端
package design.jdkObserver;
import design.observer.EventMode;
import java.util.Observer;
/**
* Created by wangtengfei1 on 2017/9/13.
*/
public class JdkRemoteControl {
public static void main(String[] args) throws Exception {
HomeVideo homeVideo = new HomeVideo();
Observer audio = new Audio(homeVideo);
Observer bulb = new Bulb(homeVideo);
Observer windowCurtains = new WindowCurtains(homeVideo);
//或者採用下面的這種方式,是等效的
/**
* Observer audio = new Audio();
* homeVideo.addObserver(audio);
*/
System.out.println(">>遙控器按下播放按鈕");
homeVideo.turn(EventMode.VIDEO);
Thread.sleep(100);
System.out.println(">>100後影片播放完畢");
homeVideo.turn(EventMode.NORMAL);
}
}
輸出結果和上面以後,就不在貼出了,可以看出,jdk實現的更簡潔,並且安全性也更高。
tomcat實現
tomcat在監聽容器聲明週期事件的時候也採用了觀察者模式,但是tomcat並沒有採用jdk實現的觀察者模式,而是自己實現了一套觀察者模式,因爲它需要不同的監聽器,包括對聲明週期的監聽,對容器的監聽,這樣能更加契合它本身的業務。看一下tomcat是怎麼實現的。
首先觀察對象要實現LifecycleListener接口的LifecycleEvent方法。這個方法接收一個LifecycleEvent對象,來標明此時發生了什麼事兒。
而監聽對象則是LifecycleBase類,這個類提供了addLifecycleListener、findLifecycleListeners、removeLifecycleListener、fireLifecycleEvent這些方法,分別是增加監聽器,查找監聽器,移除監聽器和通知各個監聽器這些方法。調用通知的方法在什麼地方呢,就是在於容器狀態改變時,例如Server啓動時,在StandardServer在調用start方法啓動,然後方法內調到startInternal方法中,會傳遞一個Configure_start事件,然後調用fireLifecycleEvent通知到各個監聽者
public interface LifecycleListener {
/**
* Acknowledge the occurrence of the specified event.
*
* @param event LifecycleEvent that has occurred
*/
public void lifecycleEvent(LifecycleEvent event);
}
org.apache.catalina.core. StandardServer監聽器通知方式 fireLifecycleEvent
@Override
protected void startInternal() throws LifecycleException {
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
setState(LifecycleState.STARTING);
globalNamingResources.start();
// Start our defined Services
synchronized (servicesLock) {
for (int i = 0; i < services.length; i++) {
services[i].start();
}
}
}