觀察者模式與事件監聽機制


一、觀察者模式

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接口,需要注冊在事件源上才能被調用。它監聽事件,並進行事件處理或者轉發。

然而,事件監聽機制與觀察者模式的關系呢?

Untitled

觀察者(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);
	}
}

輸出結果:

模擬門打開事件
前門打開
后門打開,警示燈亮起
模擬門關閉事件
前門關閉
后門關閉,警示燈熄滅

另一示例:

Button 按鈕事件監聽-又說觀察者模式

參考:

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

5、http://ericliu1986.iteye.com/blog/629562

6、http://blog.csdn.net/xiaolang85/article/details/5316859


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM