1.定義
定義對象間的一種一對多的依賴關系咳燕。當一個對象的狀態(tài)發(fā)生改變時咐刨,所有依賴于它的對象都得到通知并被自動更新。
2.類圖
- Subject
目標對象愁憔。具備功能點:1.管理多個觀察者唧领。2.提供對觀察者的注冊及退訂藻雌。3.當狀態(tài)發(fā)生改變時,對訂閱有效的觀察者進行通知疹吃。
- Observer
觀察者接口蹦疑。定義目標通知后更新操作
- ConcreteSubject
具體的主題,用來維護自身的狀態(tài)的變化
- ConcreteObserver
觀察者具體實現(xiàn)對象萨驶,用來接受目標的通知歉摧,并進行后續(xù)的操作。
3.深入理解
- 1.目標和觀察者的關系按定義區(qū)分:一對多。
- 2.單向依賴關系叁温。只有觀察者依賴與目標再悼,目標不會依賴與觀察者。
建議:
- 目標接口定義膝但,建議在類名稱后面加上Subject
- 觀察者接口定義冲九,建議在類名稱后面加上Observer
- 觀察者接口方法,建議命名為update
4.代碼
- Subject
/**
* 目標對象跟束,它知道觀察它的觀察者莺奸,并提供注冊和刪除觀察者的接口
*/
public class Subject {
/**
* 用于保存觀察者對象
*/
private List<Observer> observers = new ArrayList<>();
/**
* 注冊觀察者
*
* @param observer
*/
public void attach(Observer observer) {
observers.add(observer);
}
/**
* 刪除觀察者對象
*
* @param observer
*/
public void detach(Observer observer) {
observers.remove(observer);
}
/**
* 通知所有注冊的觀察者對象
*/
protected void notifyObservers() {
observers.stream().forEach(observer -> observer.update(this));
}
}
- Observer
/**
* 觀察者接口,定義一個更新的接口給那些在目標發(fā)生改變的時候被通知的后的操作
*/
public interface Observer {
/**
* 更新接口
*
* @param subject
*/
void update(Subject subject);
}
- ConcreteSubject
/**
* 具體的目標對象冀宴,負責把有關狀態(tài)存入到相應的觀察者
* 并在自己狀態(tài)發(fā)生改變時灭贷,通知各個觀察者
*/
public class ConcreteSubject extends Subject {
/**
* 示意目標對象
*/
private String subjectState;
public String getSubjectState() {
return subjectState;
}
public void setSubjectState(String subjectState) {
this.subjectState = subjectState;
System.out.println("目標對象已更新自身狀態(tài)為:" + this.subjectState);
this.notifyObservers();
}
}
- ConcreteObserver
/**
* 具體的觀察者對象,實現(xiàn)更新方法略贮,使得自身的狀態(tài)和目標狀態(tài)保持一致
*/
public class ConcreteObserver implements Observer {
/**
* 示意甚疟,觀察者對象狀態(tài)
*/
private String observerState = "呀,我是觀察者初始狀態(tài)逃延!";
@Override
public void update(Subject subject) {
System.out.println("觀察者之前的狀態(tài)為:" + this.observerState);
observerState = ((ConcreteSubject) subject).getSubjectState();
System.out.println("觀察者接收到更新的對象狀態(tài):" + observerState);
}
}
- ObserverClient
/**
* 調用示例
*/
public class ObserverClient {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
Observer observer=new ConcreteObserver();
subject.attach(observer);
subject.setSubjectState("121");
}
}
5. 觀察者兩種通知模式(推模型和拉模型)
5.1 推模型
目標對象主動向觀察者推送目標的詳細信息览妖,不管觀察者是否需要,推送的信息通常是目標對象的全部或者部分數(shù)據(jù)揽祥,相當于在廣播讽膏。
5.2 拉模型
目標對象在通知觀察者的時候,只傳遞少量信息盔然。如果觀察者需要更具體的信息桅打,由觀察者主動到目標對象中獲取是嗜,相當于是觀察者從目標對象中拉數(shù)據(jù)愈案。
拉模型的處理方式,會把目標對象自身通過update方法傳遞給觀察者鹅搪,這樣在觀察者需要獲取數(shù)據(jù)的時候站绪,就可以通過這個引用來獲取了。
上面的代碼就是典型的拉模型丽柿。在具體的主題里恢准,通過update方法把自身傳遞給具體的觀察者。
5.3 如何將上面的拉模型轉變?yōu)橥颇P?/h4>
處理步驟
- 1.主題類的批量通知方法甫题,增加需要通知的參數(shù)
- 2.具體觀察者的update方法接受具體通知信息
- PushSubject
public class PushSubject {
private List<PushObserver> observers = new ArrayList<>();
/**
* 訂閱主題
*
* @param observer
*/
public void attach(PushObserver observer) {
observers.add(observer);
}
/**
* 取消訂閱
*
* @param observer
*/
public void detach(PushObserver observer) {
observers.remove(observer);
}
/**
* 拉模型通知所有的注冊者
*/
protected void notifyObservers(String content) {
observers.stream().forEach(observer -> {observer.update(content);});
}
}
- ConcretePushSubject
public class ConcretePushSubject extends PushSubject {
/**
* 示意目標對象
*/
private String subjectState;
public String getSubjectState() {
return subjectState;
}
public void setSubjectState(String subjectState) {
this.subjectState = subjectState;
System.out.println("目標對象已更新自身狀態(tài)為:" + this.subjectState);
this.notifyObservers(subjectState);
}
}
- PushObserver
public interface PushObserver {
/**
* 更新接口
* 傳入更新的內容
* @param content
*/
void update(String content);
}
- ConcretePushObserver
public class ConcretePushObserver implements PushObserver {
@Override
public void update(String content) {
System.out.print("主題推送的信息:" + content);
}
}
- Main
public static void main(String[] args) {
ConcretePushSubject pushSubject=new ConcretePushSubject();
ConcretePushObserver pushObserver=new ConcretePushObserver();
pushSubject.attach(pushObserver);
pushSubject.setSubjectState("1212");
}
5.4 如何選擇拉模型與推模型
推模型是假定主題對象知道觀察者需要的數(shù)據(jù)馁筐。退模型的一個缺點就是:可能會使得觀察者對象難以復用,因為觀察者對象的update方法是按需而定義的坠非。ps:為了避免這樣的問題敏沉,主題對象通常定義一個公用的話題。類比:微信公眾號,公眾號更新內容盟迟,直接推送給訂閱的用戶秋泳。
拉模型是主題對象無法得知觀察者具體需要什么數(shù)據(jù),干脆把自身對象傳遞給觀察者攒菠,讓觀察者按需提取數(shù)據(jù)迫皱。
6.Java中的觀察者
Java語言已經內置好了觀察者的實現(xiàn)。
在java.util包中定義了Observable辖众,代表具備可以被觀察的能力卓起。也就是我們之前定義的Subject類。目標接口
在java.util包中也定義了一個接口Observer凹炸,代表觀察者對象接口既绩。里面定義了update方法
6.1好處
- 1.無須額外定義觀察者和目標對象的接口,JDK已經內置还惠。
- 2.具體的目標實現(xiàn)里面無須再維護觀察者的注冊信息饲握,這個在Observable類中已經定義好
- 3.觸發(fā)方式要先調用setChanged()方法
- 4.具體的觀察者的實現(xiàn)里,update方法能夠同時支持推模型和拉模型蚕键。
6.2代碼
- 1.定義主題類
import java.util.Observable;
public class NewPaper extends Observable {
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
this.setChanged();
//this.notifyObservers();//拉模型
this.notifyObservers(content);//推模型
}
}
- 2.定義觀察者類
import java.util.Observable;
import java.util.Observer;
public class Reader implements Observer {
//讀者名稱
private String name;
public Reader(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void update(Observable o, Object arg) {
System.out.println(name + "收到報紙了救欧,目標推送過來的內容是:" + arg);
//System.out.println(name + "收到報紙了,主動獲取到目標更新的內容:" + ((NewPaper) o).getContent());
}
}
- 3.客戶端調用
public class Program {
public static void main(String[] args) {
NewPaper paper = new NewPaper();
Reader zhangsan = new Reader("zhangsan");
Reader lisi = new Reader("lisi");
paper.addObserver(zhangsan);
paper.addObserver(lisi);
paper.setContent("iphone 8發(fā)布了B喙狻笆怠!");
}
}
7.觀察者優(yōu)缺點
7.1 優(yōu)點
- 1.觀察者模式實現(xiàn)了觀察者與目標之間的抽象耦合
- 2.觀察者模式實現(xiàn)了動態(tài)聯(lián)動
- 3.觀察者模式實現(xiàn)了廣播通信。
7.2 缺點
- 1.可能會引起無謂的操作
因為不管觀察者是否需要誊爹,都需要調用update方法蹬刷。
8.觀察者的本質
觀察者的本質是:觸發(fā)聯(lián)動
當修改目標對象的狀態(tài)時候,就會觸發(fā)相應的通知频丘,然后會循環(huán)調用所有注冊的觀察者對象的相應方法办成。相當于聯(lián)動調用這些觀察者的方法
這個聯(lián)動還是動態(tài)的,可以通過注冊和取消注冊來控制觀察者搂漠。同事目標對象和觀察者對象的解耦迂卢,又保證了無論觀察者發(fā)生怎樣的變化,目標對象總是能夠正確的聯(lián)動過來桐汤。
9.何時選用觀察者
1.當一個抽象模型有兩個方面而克,一個方面依賴于另一個方面的狀態(tài)改變。
2.在更改一個對象時候怔毛,需要同時連帶改變其他的對象员萍,而且不知道究竟有多少對象需要被連帶改變。
3.當一個對象必須通知其他的對象拣度,但是又希望這個對象和其他被它通知的對象是松散耦合的碎绎。
10.與其他設計模式的區(qū)別
10.1 觀察者模式與狀態(tài)模式
兩者是有相似之處蜂莉。但也有所不同,觀察者重心是觸發(fā)聯(lián)動混卵。但是到底決定哪些觀察者進行聯(lián)動映穗,可以配合狀態(tài)模式。
- 觀察者模式
當目標狀態(tài)發(fā)生改變時幕随,觸發(fā)并通知觀察者蚁滋,讓觀察者去執(zhí)行相應的操作。
- 狀態(tài)模式
根據(jù)不同的狀態(tài)赘淮,選擇不同的實現(xiàn)辕录。這個實現(xiàn)類的主要功能就是針對狀態(tài)相應的操作。
10.2 觀察者模式與中介者模式
兩個模式可以一起使用梢卸。比如觀察者模式里面的主題與觀察者交互比較復雜走诞,那么就可以一起使用。