一、觀察者模式
1.1 概述
有時被稱作發布/訂閱模式,觀察者模式定義了一種一對多的依賴關系,讓多個觀察者對象同時監聽某一個主題對象。這個主題對象在狀態發生變化時,會通知所有觀察者對象,使它們能夠自動更新自己。它類似B/S架構模式,構建一個服務端,多個客戶端顯示。其實這個主題對象就像是一個信息源,當信息源的狀態發送變化時,它會通知所有訂閱者,使它們進行相應的處理。在百度百科中的例子是,用戶界面可以作為一個觀察者,業務數據是被觀察者,用戶界面觀察業務數據的變化,發現數據變化后,就顯示在界面上。
1.2 模式中的參與者
- 抽象主題(Subject):它把所有觀察者對象的引用保存到一個聚集里,每個主題都可以有任何數量的觀察者。抽象主題提供一個接口,可以增加和刪除觀察者對象,以及通知所有觀察者。
- 具體主題(ConcreteSubject):將有關狀態存入具體觀察者對象;當具體主題內部狀態放生改變時,通知所有注冊過的觀察者。
- 抽象觀察者(Observer):為所有的具體觀察者定義一個接口,在得到主題通知時更新自己。
- 具體觀察者(ConcreteObserver):實現抽象觀察者角色所要求的更新接口,以便使本身的狀態與主題狀態保持一致。
注意:
觀察者的信息是來源於主題中notify方法所傳遞信息,比如notify(String str)傳遞字符str信息,觀察者update(String strObj)就會接收str信息,並進行相關操作。
1.3 適用性
1.當一個抽象模型有兩個方面, 其中一個方面依賴於另一方面。將這二者封裝在獨立的對象中以使它們可以各自獨立地改變和復用。
2.當對一個對象的改變需要同時改變其它對象, 而不知道具體有多少對象有待改變。
3.當一個對象必須通知其它對象,而它又不能假定其它對象是誰。換言之, 你不希望這些對象是緊密耦合的。
1.4 舉例說明
公司開會,領導先通知各部門的負責人去會議室(注冊、登記過程),在會議室中,大家都在等待領導的講話(多個觀察者關注同一個主題對象),領導開始說道,“今年的時間已過半了,可是業績還沒有完成預計的40%啊,大家可得努力”(通知過程),大家聽到領導這番話時,市場部經理開始說話,“由於上半年的整個市場比較萎靡,因此市場拓展方面比較緩慢,但是經過上半年的調查,我們掌握了比較重要的信息,下半年,我們市場部肯定會拓展更多的份額”;研發部經理,“我們研發部會繼續加班加點,保證完成任務”(各個觀察者接到通知后的處理過程)。
ILeader:抽象主題(Subject)
public interface ILeader
{
// 注冊登記
public void addManager(IManager manager);
// 移除已注冊的
public void removeManager(IManager manager);
// 通知所有經理
public void notifyManagers(String str);
}
MyLeader:具體主題(ConcreteSubject)
public class MyLeader implements ILeader
{
// 存儲所有需注冊登記的經理
private List<IManager> managerList = new ArrayList<IManager>();
@Override
public void addManager(IManager manager)
{
synchronized (this)
{
if (manager != null && !(managerList.contains(manager)))
{
managerList.add(manager);
}
}
}
@Override
public void removeManager(IManager manager)
{
synchronized (this)
{
if (managerList.contains(manager))
{
managerList.remove(manager);
}
}
}
@Override
public void notifyManagers(String str)
{
for (IManager iManager : managerList)
{
iManager.update(str);
}
}
}
IManager:抽象觀察者(Observer)
public interface IManager
{
/**
* 更新
* @param str 與ILeader的通知參數一致
*/
public void update(String str);
}
MarketingManager:具體觀察者(ConcreteObserver)
public class MarketingManager implements IManager
{
@Override
public void update(String str)
{
System.out.print("市場部接收到命令: ");
doSomething(str);
}
private void doSomething(String str)
{
if (str.equals("努力"))
{
System.out.println("下半年,我們市場部肯定會拓展更多的份額");
}
}
}
DevelopManager:具體觀察者(ConcreteObserver)
public class DevelopManager implements IManager
{
@Override
public void update(String str)
{
System.out.print("研發部接收到命令: ");
doSomething(str);
}
private void doSomething(String str)
{
System.out.println("我們研發部會繼續加班加點,保證完成任務");
}
}
測試類:
public class TestMain
{
public static void main(String[] args)
{
ILeader leader = new MyLeader();
IManager marketManager = new MarketingManager(); // 市場部
IManager developManager = new DevelopManager(); // 研發部
// 注冊,登記過程
// 方式一
leader.addManager(marketManager);
leader.addManager(developManager);
// 方式二 匿名:以工程部為例
leader.addManager(new IManager() // 工程部
{
@Override
public void update(String str)
{
System.out.print("工程部接收到命令: ");
if (str.equals("努力"))
{
doSomething();
}
}
private void doSomething()
{
System.out.println("我們工程部會加快工程的實施進度");
}
});
System.out.println("領導講話:先談談上半年的業績!");
// 通知過程
System.out.println("發送努力命令");
leader.notifyManagers("努力");
}
}
輸出結果:
領導講話:先談談上半年的業績!
發送努力命令
市場部接收到命令: 下半年,我們市場部肯定會拓展更多的份額
研發部接收到命令: 我們研發部會繼續加班加點,保證完成任務
工程部接收到命令: 我們工程部會加快工程的實施進度
在實際中,為了方便性,我們更多使用的是第二種注冊方式。
1.5 我推你拉
觀察者模式在關於目標(主題)角色、觀察者角色通信的具體實現中,有兩個版本。
1) 拉模式:目標角色在發生變化后,僅僅告訴觀察者角色“我變化了”;觀察者角色如果想要知道具體的變化細節,則就要自己從目標角色的接口中得到。拉模式是想要就主動表白獲取。
2) 推模式:通知你發生變化的同時,通過一個參數將變化的細節傳遞到觀察者角色中去。推模式是管你要不要,先給你啦。
這兩種模式的使用,取決於系統設計時的需要。如果目標角色比較復雜,並且觀察者角色進行更新時必須得到一些具體變化的信息,則“推模式”比較合適。如果目標角色比較簡單,則“拉模式”就很合適啦。
二、事件監聽機制
當事件源對象上發生操作時,它將會調用事件監聽器的一個方法,並在調用該方法時傳遞事件對象過去。
2.1 組成結構
Java中的事件監聽機制主要由事件源、事件對象、事件監聽器三個部分組成。
1)事件源(event source):
具體的事件源,比如說,你點擊一個button,那么button就是event source,要想使button對某些事件進行響應,你就需要注冊特定的listener。
2)事件對象(event object):
一般繼承自java.util.EventObject類,封裝了事件源對象以及與事件相關的信息。它是在事件源和事件監聽器之間傳遞信息的。
3)事件監聽器(event listener):
實現java.util.EventListener接口,需要注冊在事件源上才能被調用。它監聽事件,並進行事件處理或者轉發。
然而,事件監聽機制與觀察者模式的關系呢?
觀察者(Observer)相當於事件監聽者,被觀察者(Observable)或者說主題(Subject)相當於事件源和事件,執行邏輯時通知observer即可觸發oberver的update,同時可傳被觀察者和參數。
其實事件機制中的“事件對象”就相當於上例觀察者模式中的notify中的String參數對象。
2.2 舉例說明
DoorEvent:事件對象(event object)
public class DoorEvent extends EventObject
{
private String doorState = ""; // 表示門的狀態,有“開”和“關”兩種
public DoorEvent(Object source)
{
super(source);
}
public void setDoorState(String doorState)
{
this.doorState = doorState;
}
public String getDoorState()
{
return this.doorState;
}
}
IDoorListener:事件監聽器(event listener)
public interface IDoorListener extends EventListener
{
//EventListener是所有事件偵聽器接口必須擴展的標記接口、因為它是無內容的標記接口、
//所以事件處理方法由我們自己聲明如下:
public void dealDoorEvent(DoorEvent event);
}
FrontDoorListener:事件監聽器(event listener)
public class FrontDoorListener implements IDoorListener
{
/**
* 做具體的開門,關門動作
* @param event
*/
@Override
public void dealDoorEvent(DoorEvent event)
{
if (event.getDoorState()!=null && event.getDoorState().equals("open"))
{
System.out.println("前門打開");
}
else
{
System.out.println("前門關閉");
}
}
}
DoorManager:事件源(event source)
public class DoorManager
{
private List<IDoorListener> listeners = new ArrayList();
public void addDoorListener(IDoorListener listener)
{
synchronized (this)
{
if (listener != null && !(listeners.contains(listener)))
{
listeners.add(listener);
}
}
}
public void removeDoorListener(IDoorListener listener)
{
synchronized (this)
{
if (listeners.contains(listener))
{
listeners.remove(listener);
}
}
}
public void notifyDoors(DoorEvent event)
{
for (IDoorListener iDoorListener : listeners)
{
iDoorListener.dealDoorEvent(event);
}
}
/**
* 模擬開門事件
*/
public void fireOpend()
{
if (listeners == null)
{
return;
}
DoorEvent event = new DoorEvent(this);
event.setDoorState("open");
notifyDoors(event);
}
}
測試類:
public class TestMain
{
public static void main(String[] args)
{
DoorManager doorManager = new DoorManager();
// 添加監聽器
doorManager.addDoorListener(new FrontDoorListener());
doorManager.addDoorListener(new IDoorListener()
{
@Override
public void dealDoorEvent(DoorEvent event)
{
if (event.getDoorState() != null && event.getDoorState().equals("open"))
{
System.out.println("后門打開,警示燈亮起");
}
else
{
System.out.println("后門關閉,警示燈熄滅");
}
}
});
// 模擬事件
System.out.println("模擬門打開事件");
doorManager.fireOpend();
System.out.println("模擬門關閉事件");
DoorEvent doorEvent = new DoorEvent(doorManager);
doorEvent.setDoorState("close");
doorManager.notifyDoors(doorEvent);
}
}
輸出結果:
模擬門打開事件
前門打開
后門打開,警示燈亮起
模擬門關閉事件
前門關閉
后門關閉,警示燈熄滅
另一示例:
參考:
1、http://www.cnblogs.com/wangjq/archive/2012/07/12/2587966.html
2、http://www.cnblogs.com/abcdwxc/archive/2007/09/19/898856.html
3、http://blog.csdn.net/ai92/article/details/375691
4、http://enjiex.iteye.com/blog/1067650