小帥的公司最近在開發一套銷售系統,老闆想把上個月,這個月和下個月預計的銷售數據分別在PC,平板電腦,手機端顯示,以便多維度激勵銷售人員拼搏努力。
小帥心想,這有何難?刷刷刷,立馬設計了一個類。
項目組的老王湊過來看了看:”你的設計有一個明顯的問題,不同平臺的代碼都強耦合在一個類裏。導致以後增加或減少顯示平臺的時候都要修改主程序。這是典型的針對具體的實現編程,而不是針對接口編程。“
認識觀察者模式
那你有什麼辦法解決上面的問題呢?你行你上啊?小帥不服氣的說。
老王嘿嘿笑着:我還真有辦法,用觀察者模式再合適不過了。
觀察者模式(Observer Pattern):定義對象間的一種一對多依賴關係,使得每當一個對象狀態發生改變時,其相關依賴對象皆得到通知並被自動更新。
觀察者可以動態的添加和刪除,不會影響主題類。
這裏是標準的觀察者模式類圖(圖片參考:《設計模式》)
我們來實現一下,但是我們這個例子裏稍有變化,代碼如下:
主題
public interface Subject {
void attachObserver(Observer observer);
void detachObserver(Observer observer);
void notifyObserver();
}
import java.util.ArrayList;
import java.util.List;
public class SalesDataSubject implements Subject{
private List<Observer> observers = new ArrayList<Observer>();
// 上個月銷售額
private double lastMonthSales = 90;
// 這個月銷售額
private double thisMonthSales = 100;
// 下個月銷售額預計
private double nextMonthSales = 105;
public void attachObserver(Observer observer) {
observers.add(observer);
}
public void detachObserver(Observer observer) {
observers.remove(observer);
}
public void notifyObserver() {
for (Observer observer : observers) {
observer.update(lastMonthSales, thisMonthSales, nextMonthSales);
}
}
}
觀察者
public interface Observer {
void update(double lastMonthSales, double thisMonthSales, double nextMonthSales);
}
public class PcObserver implements Observer{
public void update(double lastMonthSales, double thisMonthSales, double nextMonthSales) {
System.out.println("PC上顯示報表,上個月銷售金額:" + lastMonthSales + "萬,這個月銷售金額:" +
thisMonthSales + "萬,下個月預計銷售金額:" + nextMonthSales + "萬");
}
}
public class IpadObserver implements Observer{
public void update(double lastMonthSales, double thisMonthSales, double nextMonthSales) {
System.out.println("ipad上顯示報表,上個月銷售金額:" + lastMonthSales + "萬,這個月銷售金額:" +
thisMonthSales + "萬,下個月預計銷售金額:" + nextMonthSales + "萬");
}
}
public class MobileObserver implements Observer{
public void update(double lastMonthSales, double thisMonthSales, double nextMonthSales) {
System.out.println("手機上顯示報表,上個月銷售金額:" + lastMonthSales + "萬,這個月銷售金額:" +
thisMonthSales + "萬,下個月預計銷售金額:" + nextMonthSales + "萬");
}
}
測試
public class TestObserver {
public static void main(String[] args) {
SalesDataSubject SalesDataSubject = new SalesDataSubject();
PcObserver pcObserver = new PcObserver();
IpadObserver ipadObserver = new IpadObserver();
MobileObserver mobileObserver = new MobileObserver();
// 添加觀察者
SalesDataSubject.attachObserver(pcObserver);
SalesDataSubject.attachObserver(ipadObserver);
SalesDataSubject.attachObserver(mobileObserver);
// 通知觀察者
SalesDataSubject.notifyObserver();
// 刪除ipad觀察者
SalesDataSubject.detachObserver(ipadObserver);
System.out.println("刪除ipad端顯示--------");
// 通知觀察者
SalesDataSubject.notifyObserver();
}
}
運行結果
PC上顯示報表,上個月銷售金額:90.0萬,這個月銷售金額:100.0萬,下個月預計銷售金額:105.0萬
ipad上顯示報表,上個月銷售金額:90.0萬,這個月銷售金額:100.0萬,下個月預計銷售金額:105.0萬
手機上顯示報表,上個月銷售金額:90.0萬,這個月銷售金額:100.0萬,下個月預計銷售金額:105.0萬
刪除ipad端顯示--------
PC上顯示報表,上個月銷售金額:90.0萬,這個月銷售金額:100.0萬,下個月預計銷售金額:105.0萬
手機上顯示報表,上個月銷售金額:90.0萬,這個月銷售金額:100.0萬,下個月預計銷售金額:105.0萬
總結
這樣Subject(主題)和Observer(觀察者)之間就實現了鬆耦合,主題不知道也不用關心一共有多少觀察者,主題只要調用notifyObserver()方法就可以通知所有的觀察者了。
觀察者的新增和減少,完全不會影響到主題,對主題來說無感知,實現瞭解耦。
”上面的例子使用的是推模型(push model),主題主動把具體的數據推給觀察者,還有一種是拉模型(pull model) 主題把自己的對象作爲參數傳給觀察者,觀察者自己要什麼數據通過主題對象直接取就行了。“老王補充道。
”觀察者模式有麼多優點難道就沒有缺點嗎?“小帥顯得有點不服氣。
老王,想了想,說道:
缺點還是有的,畢竟沒有完美的設計嘛。
比如主題不知道觀察者的具體的實現,對更新觀察者狀態的代價一無所知,可能一個很小的操作都能引起一系列觀察者以及依賴這些觀察者對象的更新。多少頻率更新一次纔好?主題也很慌的好嘛。。。
還有,雖然觀察者可以隨時知道主題發生了變化,但是觀察者模式沒有相應的機制使觀察者知道主題對象具體發生了什麼變化。主題到底改了什麼?觀察者一臉懵逼。。。
”雖然有一些缺點,但是優勢更加明顯,觀察者模式有點意思",小帥若有所思。
”觀察者模式的應用場景非常廣泛,小到代碼層面的解耦,大到架構層面的系統解耦,再或者一些產品的設計思路,都有這種模式的影子,比如,郵件訂閱、RSS Feeds,本質上都是觀察者模式。不同的應用場景和需求下,這個模式也有截然不同的實現方式,有同步阻塞的實現方式,也有異步非阻塞的實現方式;有進程內的實現方式,也有跨進程的實現方式。。。“
老王越講越有勁,回頭一看,小帥人都不知道跑哪裏去了。。。