何時使用觀察者模式
1、觸發聯動:當修改目標狀態時就會觸發相應的通知,然后會循環調用所有注冊的觀察者對象的相應方法。
2、建議在下面三種情況下使用觀察者模式:
1.當一個抽象模型有兩個方面,其中一個方面的操作依賴于另一個方面的狀態變化時
2.如果在封蓋一個對象的時候,需要同時連帶改變其他的對象,而且不知道究竟應該有對少對象需要被連帶改變
3.當一個對象必須通知其他的對象,但是你又希望這個對象和其他被他通知的對象是松散耦合的
實現方式一:觀察者模式 經典模版
觀察者模式的結構:Subject(被觀察者或者說是目標類):要有添加觀察者,刪除觀察者,和提醒觀察者(當被觀察者的狀態發生改變的時候調用這個方法)的方法,Observe(觀察者):要有更新方法(當觀察者狀態發生改變調用提醒方法后觀察者通過更新方法來做出不同響應(動作))。
ConcreteSubject是Subject接口的實現類
ConcreteObserver是Observer接口的實現類
目標(被觀察者):
/**
* 目標對象,它知道觀察它的觀察者,并提供注冊(添加)和刪除觀察者的接口
*/
public class Subject {
? ? // 用來保存注冊的觀察者對象
? ? private List<Observer> observers = new ArrayList<Observer>();
? ? // attach detach notifyObservers
? ? // 把訂閱天氣的人添加到訂閱者列表中
? ? public void attach(Observer observer) {
? ? ? ? observers.add(observer);
? ? }
? ? /**
? ? * 刪除集合中的指定觀察者
? ? * @param observer
? ? */
? ? public void detach(Observer observer) {
? ? ? ? observers.remove(observer);
? ? }
? ? /**
? ? * 通知所有注冊的觀察者對象
? ? */
? ? protected void notifyObservers() {
? ? ? ? for (Observer observer : observers) {
? ? ? ? ? ? observer.update(this);
? ? ? ? }
? ? }
}
目標(被觀察者)具體實現:
/**
* 具體的目標對象,負責把有關狀態存入到相應的觀察者對象中
*/
public class ConcreteSubject extends Subject {
? ? // 目標對象的狀態
? ? private String Content;
? ? public String getSubjectState() {
? ? ? ? return Content;
? ? }
? ? public void setSubjectState(String Content) {
? ? ? ? this.Content = Content;
? ? ? ? // 內容有了,通知所有的訂閱的人
? ? ? ? this.notifyObservers();
? ? }
}
觀察者接口:
/**
* 這是一個觀察者接口,定義一個更新的接口給那些在目標發生改變的時候被通知的對象
*/
public interface Observer {
? ? /**
? ? * 更新的接口 傳入目標對象,方便獲取相應的目標對象的狀態
? ? * @param subject
? ? */
? ? void update(Subject subject);
}
觀察者接口的具體實現:
/**
* 具體的觀察者對象,實現更新的方法,使自身的狀態和目標的狀態保持一致
*/
public class ConcreteObserver implements Observer {
? ? // 觀察者的名字,是誰收到了這個訊息
? ? private String observerName;
? ? // 觀察者的狀態,這個消息從目標處獲取
? ? private String observerState;
? ? // 提醒的內容
? ? private String remindTing;
? ? /**
? ? * 獲取目標類的狀態同步到觀察者的狀態中
? ? */
? ? @Override
? ? public void update(Subject subject) {
? ? ? ? observerState = ((ConcreteSubject) subject).getSubjectState();
? ? ? ? System.out.println(observerName + "收到了, " + observerState + " , " + remindTing);
? ? }
? ? public String getObserverName() {
? ? ? ? return observerName;
? ? }
? ? public void setObserverName(String observerName) {
? ? ? ? this.observerName = observerName;
? ? }
? ? public String getRemindTing() {
? ? ? ? return remindTing;
? ? }
? ? public void setRemindTing(String remindTing) {
? ? ? ? this.remindTing = remindTing;
? ? }
}
測試類:
? ? public static void main(String[] args) {
? ? ? ? // 1.創建目標
? ? ? ? ConcreteSubject weather = new ConcreteSubject();
? ? ? ? // 2.創建觀察者
? ? ? ? ConcreteObserver observerGirl = new ConcreteObserver();
? ? ? ? observerGirl.setObserverName("小明的女朋友");
? ? ? ? observerGirl.setRemindTing("是我們的第一次約會,地點街心公園,不見不散哦");
? ? ? ? ConcreteObserver observerMum = new ConcreteObserver();
? ? ? ? observerMum.setObserverName("老媽");
? ? ? ? observerMum.setRemindTing("是一個購物的好日子,明天去天虹掃貨");
? ? ? ? // 3.注冊觀察者
? ? ? ? weather.attach(observerGirl);
? ? ? ? weather.attach(observerMum);
? ? ? ? // 4.目標發布天氣
? ? ? ? weather.setSubjectState("#明天天氣晴朗,藍天白云,氣溫28度#");
? ? }
實現方式二:利用Java提供的觀察者實現 觀察者模式
Java 實現 VS 自己實現的對比四點:
(1)不需要再定義觀察者和目標接口(JDK已經定義)。
(2)具體的目標實現里面不需要再維護觀察者的注冊信息,Java中的Observable類里面已經實現。
(3)觸發通知的方式有一點變化,要先調用setChanged方法,這個是Java為了幫助實現更精確的觸發控制而提供的功能。
(4)具體觀察者的實現里面,update方法其實能同時支持推模型和拉模型,這個Java在定義的時候,已經考慮。
目標(被觀察者):
/**
* 被觀察者的具體實現
*/
public class ConcreteSubject extends Observable {
? ? // 變化的具體內容
? ? private String content;
? ? public String getContent() {
? ? ? ? return content;
? ? }
? ? public void setContent(String content) {
? ? ? ? this.content = content;
? ? ? ? // 發生變化,就要通知所有的觀察者
? ? ? ? // 注意在通知之前,在用Java中的Observer模式的時候,下面這句話不可少
? ? ? ? this.setChanged();
? ? ? ? // 然后主動通知,這里我們先用推的方式
? ? ? ? this.notifyObservers(content);
? ? ? ? // 如果是拉的方式,我們就調用
? ? ? ? // this.notifyObservers();
? ? }
}
觀察者:
/**
* 具體的觀察者對象
*/
public class ConcreteObserver implements Observer{
? ? //觀察者名稱的變量
? ? private String observerName;
? ? @Override
? ? public void update(Observable o, Object arg) {
? ? ? ? //第一種是推的方式
? ? ? ? System.out.println(observerName+"收到了消息,目標推送過來的是"+arg);
? ? ? ? //第二種是拉的方式
? ? ? ? System.out.println(observerName+"收到了消息,主動到目標對象中去拉,拉的內容是"+((ConcreteSubject)o).getContent());
? ? }
? ? public String getObserverName() {
? ? ? ? return observerName;
? ? }
? ? public void setObserverName(String observerName) {
? ? ? ? this.observerName = observerName;
? ? }
}
測試類:
public static void main(String[] args) {
? ? ? ? // 創建一個目標,也就是被觀察者
? ? ? ? ConcreteSubject subject = new ConcreteSubject();
? ? ? ? // 創建小明的女朋友作為觀察者
? ? ? ? ConcreteObserver girl = new ConcreteObserver();
? ? ? ? girl.setObserverName("小明的女朋友");
? ? ? ? // 創建小明的老媽作為觀察者
? ? ? ? ConcreteObserver mum = new ConcreteObserver();
? ? ? ? mum.setObserverName("小明的老媽");
? ? ? ? // 注冊觀察者
? ? ? ? subject.addObserver(girl);
? ? ? ? subject.addObserver(mum);
? ? ? ? // 目標更新天氣情況了
? ? ? ? subject.setContent("天氣晴,氣溫28度");
? ? }
觀察者優缺點
1、觀察者模式的優點:
(1)觀察者模式實現了觀察者和目標之間的抽象耦合
(2)觀察者模式實現了動態聯動
(3)觀察者模式支持廣播通信
2、觀察者模式的缺點:可能會引起無謂的操作。
實現方式三:區別對待觀察者場景問題 (靈活定制觀察者)
區別觀察者模式是,目標父類不實現通知方法,在子類中實現有區別的通知方法。
區別對待的觀察者模型中和通用觀察者模型的區別在于:要根據不同的觀察者來進行不同的推送,所以區別在于目標類中的通知更新方法需要在具體的目標類中進行實現。(因為需要根據不同的情況進行更新,所以需要在具體的目標類中實現剛剛那個方法)
區別對待觀察者,邏輯判斷讓觀察者實現更符合邏輯,定義到接口觀察者中,setRule(),讓小明女朋友和老媽自己去定義
目標(被觀察者)的抽象方法:
public abstract class Subject {
? ? // 用來保存注冊的觀察者對象
? ? public List<Observer> observers = new ArrayList<Observer>();
? ? // attach detach abstract notifyObservers
? ? // 把觀察者添加到訂閱者列表中
? ? public void attach(Observer observer) {
? ? ? ? observers.add(observer);
? ? }
? ? // 刪除集合中指定的訂閱天氣的人
? ? public void detach(Observer observer) {
? ? ? ? observers.remove(observer);
? ? }
? ? protected abstract void notifyObservers();
}
目標(被觀察者)的具體實現:
public class ConcreteSubject extends Subject {
? ? // "晴天" "下雨" "下雪"
? ? // 目標對象的狀態
? ? private String Content;
? ? @Override
? ? protected void notifyObservers() {
? ? ? ? // 循環所有注冊的觀察者
? ? ? ? for (Observer observer : observers) {
? ? ? ? ? ? // 情況之一:
? ? ? ? ? ? // 如果天氣是晴天,按照小明的女朋友需要下雨的條件,小明的老媽需要下雨或下雪的條件,則她們倆就都不需要通知了。
? ? ? ? ? ? // 情況之二:
? ? ? ? ? ? // 如果天氣是下雨,則小明的女朋友需要通知,而小明的老媽也需要通知。
? ? ? ? ? ? // 情況之三:
? ? ? ? ? ? // 如果天氣是下雪,則只需要通知小明的老媽。
? ? ? ? ? ? if("下雨".equals(this.getContent())){
? ? ? ? ? ? ? ? if("小明的女朋友".equals(observer.getObserverName())){
? ? ? ? ? ? ? ? ? ? observer.update(this);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? if("小明的老媽".equals(observer.getObserverName())){
? ? ? ? ? ? ? ? ? ? observer.update(this);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? if("下雪".equals(this.getContent())){
? ? ? ? ? ? ? ? if("小明的老媽".equals(observer.getObserverName())){
? ? ? ? ? ? ? ? ? ? observer.update(this);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? }
? ? public String getContent() {
? ? ? ? return Content;
? ? }
? ? public void setContent(String content) {
? ? ? ? Content = content;
? ? ? ? this.notifyObservers();
? ? }
}
觀察者的接口:
/**
* 定義一個更新的接口方法給那些在目標發生改變的時候被通知的觀察者對象調用
*/
public interface Observer {
? ? // 更新的接口
? ? public void update(Subject subject);
? ? // 設置觀察者名稱
? ? public void setObserverName(String observerName);
? ? // 取得觀察者名稱
? ? public String getObserverName();
}
觀察者的具體實現:
public class ConcreteObserver implements Observer {
? ? // 觀察者的名稱
? ? private String observerName;
? ? // 天氣情況的內容
? ? private String content;
? ? // 提醒的內容
? ? private String remindThing;
? ? @Override
? ? public void update(Subject subject) {
? ? ? ? content = ((ConcreteSubject) subject).getContent();
? ? ? ? System.out.println(observerName + "收到了<" + content + ">," + remindThing);
? ? }
? ? @Override
? ? public void setObserverName(String observerName) {
? ? ? ? this.observerName = observerName;
? ? }
? ? @Override
? ? public String getObserverName() {
? ? ? ? return observerName;
? ? }
? ? public String getContent() {
? ? ? ? return content;
? ? }
? ? public void setContent(String content) {
? ? ? ? this.content = content;
? ? }
? ? public String getRemindThing() {
? ? ? ? return remindThing;
? ? }
? ? public void setRemindThing(String remindThing) {
? ? ? ? this.remindThing = remindThing;
? ? }
}
測試類:
public static void main(String[] args) {
? ? ? ? // 1.創建目標
? ? ? ? ConcreteSubject weather = new ConcreteSubject();
? ? ? ? // 2.創建觀察者
? ? ? ? ConcreteObserver observerGirl = new ConcreteObserver();
? ? ? ? observerGirl.setObserverName("小明的女朋友");
? ? ? ? observerGirl.setRemindThing("下雨了,安靜的呆在家里吧");
? ? ? ? ConcreteObserver observerMum = new ConcreteObserver();
? ? ? ? observerMum.setObserverName("小明的老媽");
? ? ? ? observerMum.setRemindThing("不管下雨還是下雪,我都不出門了");
? ? ? ? // 3.注冊觀察者
? ? ? ? weather.attach(observerGirl);
? ? ? ? weather.attach(observerMum);
? ? ? ? // 4.目標發布天氣
? ? ? ? weather.setContent("下雪");
? ? }