舉個慄子 問題描述 幾個同事上班期間看股市行情,讓前臺MM幫忙看著老闆什麼時候過來查崗,老闆進門的時候MM就撥電話給其中一個同事,於是所有同事都知道了,再繼續工作。。。 簡單實現 前臺秘書MM 看股票同事 測試 測試結果 存在問題 “前臺MM”和“看股票同事”互相耦合 如果還有人想看NBA直播,那隻 ...
舉個慄子
問題描述
幾個同事上班期間看股市行情,讓前臺MM幫忙看著老闆什麼時候過來查崗,老闆進門的時候MM就撥電話給其中一個同事,於是所有同事都知道了,再繼續工作。。。
簡單實現
前臺秘書MM
/**
* 前臺秘書MM
* Created by callmeDevil on 2019/7/27.
*/
public class Secretary {
// 同事列表
private List<StockObserver> observers = new ArrayList<>();
private String action;
// 增加
public void attach(StockObserver observer){
// 有幾個同事請前臺幫忙,於是就給集合增加幾個對象
observers.add(observer);
}
// 通知
public void call(){
// 待老闆來了,就給所有登記的同事們發通知
for (StockObserver observer : observers) {
observer.update();
}
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
}
看股票同事
/**
* 看股票同事
* Created by callmeDevil on 2019/7/27.
*/
public class StockObserver {
private String name;
private Secretary sub;
public StockObserver(String name, Secretary sub){
this.name = name;
this.sub = sub;
}
public void update(){
System.out.println(String.format("%s %s 關閉股票行情,繼續工作!", sub.getAction(), name));
}
}
測試
public class Test {
public static void main(String[] args) {
// 前臺妹子
Secretary mm = new Secretary();
// 看股票的同事
StockObserver observer1 = new StockObserver("哪路托", mm);
StockObserver observer2 = new StockObserver("啥是gay", mm);
// 前臺妹子記下兩位同事
mm.attach(observer1);
mm.attach(observer2);
// 發現老闆
mm.setAction("老闆回來了!");
// 通知兩個同事
mm.call();
}
}
測試結果
老闆回來了! 哪路托 關閉股票行情,繼續工作!
老闆回來了! 啥是gay 關閉股票行情,繼續工作!
存在問題
- “前臺MM”和“看股票同事”互相耦合
- 如果還有人想看NBA直播,那隻能改動“前臺MM”,不符合開放-封閉原則
- 其次應該遵循依賴倒轉原則,讓兩者之間不相互依賴
簡單實現2
抽象觀察者
/**
* 抽象觀察者
* Created by callmeDevil on 2019/7/27.
*/
public abstract class Observer {
protected String name;
protected Secretary sub;
public Observer(String name, Secretary sub) {
this.name = name;
this.sub = sub;
}
public abstract void update();
}
前臺秘書MM
/**
* 前臺秘書MM
* Created by callmeDevil on 2019/7/27.
*/
public class Secretary {
// 同事列表
private List<Observer> observers = new ArrayList<>();
private String action;
// 增加
public void attach(Observer observer) { //針對抽象編程,減少了與具體類的耦合
observers.add(observer);
}
// 減少
public void detach(Observer observer) { //針對抽象編程,減少了與具體類的耦合
observers.remove(observer);
}
// 通知
public void call() {
for (Observer observer : observers) {
observer.update();
}
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
}
看股票的同事
/**
* 看股票的同事
* Created by callmeDevil on 2019/7/27.
*/
public class StockObserver extends Observer {
public StockObserver(String name, Secretary sub) {
super(name, sub);
}
@Override
public void update(){
System.out.println(String.format("%s %s 關閉股票行情,繼續工作!", sub.getAction(), name));
}
}
看NBA的同事
/**
* 看 NBA 的同事
* Created by callmeDevil on 2019/7/27.
*/
public class NBAObserver extends Observer {
public NBAObserver(String name, Secretary sub) {
super(name, sub);
}
@Override
public void update() {
System.out.println(String.format("%s %s 關閉NBA 直播,繼續工作!", sub.getAction(), name));
}
}
測試代碼同上
存在問題
其實“前臺MM”也應該抽象出來,如果老闆回來時,MM來不及電話了,於是通知大家的任務變成誰來做?是的,這時候是老闆本人變成了通知者。
觀察者模式
定義
又叫做發佈-訂閱模式。定義了一種一對多的依賴關係,讓多個觀察者對象同事監聽某一個主題對象。這個主題對象在狀態發生變化時,會通知所有觀察者對象,使它們能夠自動更新自己。
UML圖
代碼實現
通知者介面
/**
* 通知者介面
* Created by callmeDevil on 2019/7/27.
*/
public interface Subject {
void attach(Observer observer);
void detach(Observer observer);
void call();
String getAction();
void setAction(String action);
}
老闆
/**
* 老闆
* Created by callmeDevil on 2019/7/27.
*/
public class Boss implements Subject {
// 同事列表
private List<Observer> observers = new ArrayList<>();
private String action;
@Override
public void attach(Observer observer) {
observers.add(observer);
}
@Override
public void detach(Observer observer) {
observers.remove(observer);
}
@Override
public void call() {
for (Observer observer : observers) {
observer.update();
}
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
}
抽象觀察者
/**
* 抽象觀察者
* Created by callmeDevil on 2019/7/27.
*/
public abstract class Observer {
protected String name;
protected Subject sub;
public Observer(String name, Subject sub) { // 原來是“前臺MM”,現改成“抽象通知者”
this.name = name;
this.sub = sub;
}
public abstract void update();
}
看股票的同事
/**
* 看股票的同事
* Created by callmeDevil on 2019/7/27.
*/
public class StockObserver extends Observer {
public StockObserver(String name, Subject sub) { // 原來是“前臺MM”,現改成“抽象通知者”
super(name, sub);
}
@Override
public void update(){
System.out.println(String.format("%s %s 關閉股票行情,繼續工作!", sub.getAction(), name));
}
}
“看NBA的同事”實現與“看股票的同事”類似,此處省略
測試
public class Test {
public static void main(String[] args) {
// 老闆
Boss boss = new Boss();
// 看股票的同事
StockObserver observer1 = new StockObserver("哪路托", boss);
// 看NBA的同事
NBAObserver observer2 = new NBAObserver("啥事gay", boss);
boss.attach(observer1);
boss.attach(observer2);
boss.detach(observer1); // 主角光環!斑怎麼樣都打不過哪路托!所以減去
// 斑複活了!
boss.setAction("我宇智波斑複活了!");
// 發出通知
boss.call();
}
}
測試結果
我宇智波斑複活了! 啥事gay 關閉NBA 直播,繼續工作!
總結
- 動機是什麼?將一個系統分割成一系列相互協作的類有一個很不好的副作用,那就是需要維護相關對象之間的一致性,我們不希望為了維持一致性而使各類緊密耦合,這樣會給維護、擴展和重用都帶來不便。
- 什麼時候使用?當一個對象的改變需要同時改變其他對象的時候,而且它不知道具體有多少對象待改變;一個抽象模型有兩個方面,其中一方面依賴於另一方面,這時用觀察者模式可以將這兩者封裝在獨立的對象中使它們各自獨立的改變和復用。
- 總的來講,觀察者模式所在的工作其實就是在解除耦合,讓耦合的雙方都依賴於抽象,而不是依賴於具體,從而使得各自的變化都不會影響另一邊的改變。
- 缺點?舉個慄子,比如 VS2005,當你點擊運行程式時,與運行程式以後,處理彈出一個控制台的程式窗體以外,工具欄發生了變化,工具箱不見了...。僅僅是點擊了一個“運行”按鈕,就發生這麼多變化,而各個變化都涉及到不同的控制項,並且沒辦法讓每個控制項都去實現一個“Observer”介面,也就是說不可能用介面的方式且實現觀察模式。再回過頭來看上面的慄子,儘管已經使用了依賴倒轉原則,但是“抽象通知者”還是依賴“抽象觀察者”,也就是說,萬一沒有了抽象觀察者這樣的介面,這通知的功能就完成不了了。另外就是每個具體觀察者,他不一定是“更新”的方法要調用,就像剛纔說的,我希望的是“工具箱”是隱藏,“自動視窗”是打開,這根本就不是同名的方法,這就是觀察者模式不足的地方。在 .NET 中,可以用事件委托較好的解決該缺點,有興趣的可以查看他人文章,此處不再敘述。