Java設計模式--觀察者模式

概述

是不是你平時安裝程序的時候都會讓你選擇是否訂閱通知,或者我們在網上買東西的物流過程中,每到一個新的進度點都會進行更新,以及通知。其實這裏就用到了我們的觀察者模式。

概念

觀察者模式:定義對象間的一種一對多的依賴關係。當一個對象的狀態發生改變時,所有依賴於它的對象都得到通知並自動更新。
在這裏插入圖片描述
比如我們對於天氣的訂閱,是不是當我們的天氣進行變化的時候,應用就會發出通知,告知我們天氣的變化。圖中的Subject就是天氣,Observer就是用戶。當天氣進行變化的時候Subject中的Notify方法就會被調用,這樣Observer中的Update方法就會做出相應的更新。而下面的兩個分別是上面的實現類。
觀察者模式是單向依賴的,觀察者對象的狀態是依賴於目標對象的,只有目標對象的狀態發生改變,纔會改變觀察者對象的狀態。
觀察者模式主要有兩種實現方法:推模型和拉模型。

  • 推模型

目標對象主動向觀察者推送目標的詳細信息,推送的信息通常是目標對象的全部或者部分數據。

  • 拉模型

目標對象在通知觀察者的時候只傳遞少量信息。如果觀察者需要更具體的信息,由觀察者主動到目標對象中獲取,相當於是觀察者從目標對象中拉數據,這種實現中一般會把目標對象通過Update方法傳遞給觀察者。

觀察者模式實現代碼

手動實現

我們根據類圖可以知道觀察者模式主要是由4個類,Subject,Observer,ConcreteSubject,ConcreteObserver。在Subject中我們需要定義一個集合來儲存我們的觀察者對象,並且在其中要實現觀察者的添加以及刪除,並且要完成通知所有註冊的觀察者對象的方法。
Subject.java(推)

package com.xjh.observer;

import java.util.ArrayList;
import java.util.List;

/**
 * 目標對象,它知道觀察它的觀察者,並提供註冊和刪除觀察者的接口
 * @author Gin
 *
 */
public class Subject {

	//保存註冊的觀察者對象
	private List<Observer> observers = new ArrayList<Observer>();
	
	//添加觀察者
	public void attach(Observer observer) {
		observers.add(observer);
	}
	
	//刪除觀察者
	public void detach(Observer observer) {
		observers.remove(observer);
	}
	
	//通知所有註冊的觀察者對象
	protected void notifyObservers(String context) {
		for(Observer observer : observers) {
			observer.update(context);
		}
	}
}

Subject.java(拉)

package com.xjh.observer;

import java.util.ArrayList;
import java.util.List;

/**
 * 目標對象,它知道觀察它的觀察者,並提供註冊和刪除觀察者的接口
 * @author Gin
 *
 */
public class Subject {

	//保存註冊的觀察者對象
	private List<Observer> observers = new ArrayList<Observer>();
	
	//添加觀察者
	public void attach(Observer observer) {
		observers.add(observer);
	}
	
	//刪除觀察者
	public void detach(Observer observer) {
		observers.remove(observer);
	}
	
	//通知所有註冊的觀察者對象
	protected void notifyObservers() {
		for(Observer observer : observers) {
			observer.update(this);
		}
	}
}

然後定義觀察者接口,裏面擁有Update方法
Observer.java(推)

package com.xjh.observer;
/**
 * 這是一個觀察者接口,定義一個更新的接口給那些在目標發生改變的時候被通知的對象
 * @author Gin
 *
 */
public interface Observer {

	/**
	 * 更新接口
	 * @param subject 傳入對象目標,方便獲取相應的目標對象的狀態
	 */
	public void update(String context);
}

Observer.java(拉)

package com.xjh.observer;
/**
 * 這是一個觀察者接口,定義一個更新的接口給那些在目標發生改變的時候被通知的對象
 * @author Gin
 *
 */
public interface Observer {

	/**
	 * 更新接口
	 * @param subject 傳入對象目標,方便獲取相應的目標對象的狀態
	 */
	public void update(Subject subject);
}

之後就要編寫實現類,在Subject的實現類中我們需要定義狀態,用來更新,在set方法中調用父類的notifyObservers方法進行對觀察者的通知。
ConcreteSubject.java(推)

package com.xjh.observer;
/**
 * 具體的目標對象,負責把有關狀態存人到相應的觀察者對象中
 * @author Gin
 *
 */
public class ConcreteSubject extends Subject {

	//目標對象狀態
	private String subjectState;

	public String getSubjectState() {
		return subjectState;
	}

	public void setSubjectState(String subjectState) {
		this.subjectState = subjectState;
		this.notifyObservers(subjectState);
	}
	
}

ConcreteSubject.java(拉)

package com.xjh.observer;
/**
 * 具體的目標對象,負責把有關狀態存人到相應的觀察者對象中
 * @author Gin
 *
 */
public class ConcreteSubject extends Subject {

	//目標對象狀態
	private String subjectState;

	public String getSubjectState() {
		return subjectState;
	}

	public void setSubjectState(String subjectState) {
		this.subjectState = subjectState;
		this.notifyObservers();
	}
	
}

然後我們在Observer的實現類中實現Update方法就好了。
ConcreteObserver.java(推)

package com.xjh.observer;
/**
 * 具體的觀察者對象,實現更新的方法,使自身狀態和目標的狀態保持一致
 * @author Gin
 *
 */
public class ConcreteObserver implements Observer {

	//觀察者的狀態
	private int id;
	private String observerState;
	
	public ConcreteObserver(int id) {
		this.id = id;
	}
	
	/**
	 * 獲取目標類的狀態同步到觀察者的狀態
	 */
	@Override
	public void update(String context) {
		observerState = context;
		System.out.println(id+":"+observerState);
	}

}

ConcreteObserver.java(拉)

package com.xjh.observer;
/**
 * 具體的觀察者對象,實現更新的方法,使自身狀態和目標的狀態保持一致
 * @author Gin
 *
 */
public class ConcreteObserver implements Observer {

	//觀察者的狀態
	private int id;
	private String observerState;
	
	public ConcreteObserver(int id) {
		this.id = id;
	}
	
	/**
	 * 獲取目標類的狀態同步到觀察者的狀態
	 */
	@Override
	public void update(Subject subject) {
		observerState = ((ConcreteSubject)subject).getSubjectState();
		System.out.println(id+":"+observerState);
	}

}

當然我們怎麼去使用觀察者模式呢,這裏就要編寫一個測試類:

Client.java

package com.xjh.observer;

public class Client {

	public static void main(String[] args) {
		//創建目標
		ConcreteSubject subject = new ConcreteSubject();
		//創建觀察者
		ConcreteObserver one = new ConcreteObserver(1);
		ConcreteObserver two = new ConcreteObserver(2);
		//註冊觀察者
		subject.attach(one);
		subject.attach(two);
		//目標發佈天氣
		subject.setSubjectState("明天開會");
		//刪除觀察者
		subject.detach(one);
		//目標發佈天氣
		subject.setSubjectState("後天開會");
	}

}

運行結果如下,滿足了我們的要求。
運行結果

使用Java提供的接口實現

之前爲了更好的理解觀察者模式,我們已經實現了基本功能,當時Java其實已經提供好了觀察者模式的實現。我們先說下使用Java實現與我們自己實現的不同吧。

  • 不需要再定義觀察者和目標接口
  • 具體的目標實現裏面不需要再維護觀察者的註冊信息,這個在Java中的Observable類裏面實現好了。
  • 觸發通知的方式發生改變,需要先調用setChanged方法,這個是Java爲了幫助實現更精確的觸發控制而提供的功能。
  • 具體觀察者的實現裏面,update方法其實已經能同時支持推模型和拉模型

我們使用Java所提供的接口實現的好處就是更加的規範。
我們實現Observable接口,裏面已經實現了添加,刪除以及通知的方法。
ConcreteSubject.java

package com.xjh.jdkobserver;

import java.util.Observable;
/**
 * 天氣目標具體實現類
 * @author Gin
 *
 */
public class ConcreteSubject extends Observable {
	
	private String context;

	public String getContext() {
		return context;
	}

	public void setContext(String context) {
		this.context = context;
		//通知所有觀察者
		this.setChanged();
		this.notifyObservers(context);//推模型
		//this.notifyObservers();//拉模型
	}
	
}

因爲Observable實現了推拉模型的結合,所以在重寫update方法裏面有兩個參數,分別代表的就是更新內容,以及Observable對象的引用。
ConcreteObserver.java

package com.xjh.jdkobserver;

import java.util.Observable;
import java.util.Observer;

public class ConcreteObserver implements Observer {

	//觀察者ID
	private int id;
	
	public ConcreteObserver(int id) {
		this.id = id;
	}
	
	@Override
	public void update(Observable arg0, Object arg1) {
		//推模型,推送過來消息
		System.out.println(id+":"+arg1);
		//拉模型,主動到目標對象中去拉
		//System.out.println(id+":"+((ConcreteSubject)arg0).getContext());
	}

}

然後我們在 Main函數中按照步驟實現就好了。
Client.java

package com.xjh.jdkobserver;

public class Client {

	public static void main(String[] args) {
		//創建目標
		ConcreteSubject subject = new ConcreteSubject();
		//創建觀察者
		ConcreteObserver one = new ConcreteObserver(1);
		ConcreteObserver two = new ConcreteObserver(2);
		ConcreteObserver three = new ConcreteObserver(3);
		//註冊觀察者
		subject.addObserver(one);
		subject.addObserver(two);
		subject.addObserver(three);
		//目標發佈天氣
		subject.setContext("明天開會");
		//刪除觀察者
		subject.deleteObserver(one);
		//目標發佈天氣
		subject.setContext("後天開會");
	}

}

執行結果
我們可以發現他的排序是321,但是我們add的順序是123,所以可以發現他的插入和讀取是類似與棧的形式(我猜的)。

總結

推模型是假定目標對象知道觀察者需要的數據,推模型可能會使觀察者對象難以複用。
拉模型是目標對象不知道觀察者具體需要什麼數據,因此把自身傳給觀察者,由觀察者來取值,然而在拉模型下,update方法的參數是目標對象本身,基本上可以適應各種情況的需要。

適用場景
  • 當一個抽象模型有兩個方面,其中一個方面的操作依賴於另一個方面的狀態變化
  • 如果在更改一個對象的時候,需要同時連帶改變其他的對象,而且不知道究竟應該有多少對象需要被連帶改變
  • 當一個對象必須通知其他的對象,但是你又希望這個對象和其他的被通知的對象是鬆散耦合的
優點
  • 觀察者模式實現了觀察者和目標之間的抽象耦合,解除了實現耦合
  • 觀察者模式實現了動態聯動
  • 觀察者模式支持廣播通知
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章