系統解耦(System decoupling)
觀察者模式(Observer)
和其它形式的回調函數(callback)類似,Observer模式也允許你通過挂鈎程式(hook point)改變代碼。不同之處在于,從本質上說,Observer模式是完全動态的。它經常被用于需要根據其它對象的狀态變化來改變自身(狀态)的場合,而且它還經常是事件管理系統(event management)的基本組成部分。無論什麼時候,當你需要用完全動态的方式分離呼叫源和被呼叫代碼的時候,(Observer模式都是你的首選)。(最後一句話好像沒寫完 )
Observer模式解決的是一個相當常見的問題:當某個對象改變狀态的時候,另外一組(與之相關的)對象如何更新它們自己。比如說,Smalltalk裡的“model-view”結構,(它是MVC(model-view-controller)結構的一部分),再比如基本與之相當的“文檔-視圖(document-view)”結構。假設說你有一些資料(也就是“文檔”)和多于一個的視圖,比如說是一個圖表(plot)和一個文本視圖。當你改變資料的時候,這兩個視圖必須知道進而(根據需要)更新它們自己,這也就是Observer模式所要幫你解決的問題。這個問題是如此的常見,以至于它的解決辦法已經成了标準java.util庫的一部分。
用Java實作observer模式需要用到兩種類型的對象,Observable類負責記住發生變化時需要通知哪些類,而不論“狀态”改變與否。當被觀察對象說“OK,你們(指觀察者)可以根據需要更新你們自己了,”Observable類通過調用notifyObservers()方法通知清單上的每個觀察者,進而完成這個任務。notifyObservers()是基類Observable的一個方法。
實際上,Observer模式真正變化的有兩樣東西:觀察者(observing objects)的數量和它們如何更新自己。也就是說,observer模式使得你在不必改動其它代碼的情況下隻針對這兩種變化更改代碼。
。。。。。。(作者還沒寫完 )
Observer實際上是隻有一個成員函數的接口類,這個成員函數就是update()。當被觀察者決定更新所有的觀察者的時候,它就調用update()函數。是否需要傳遞參數是可選的;即使是沒有參數的Update()函數也同樣符合observer模式;但是,更通常的做法是讓被觀察者(通過update()函數)把引起更新的對象(也就是它自己 )和其它有用的資訊傳遞給觀察者, 因為一個觀察者可能會注冊到多于一個的被觀察者。這樣,觀察者對象就不用再費勁查找是哪個被觀察者引起的更新,并且它所需要的資訊也已經傳遞過來。
決定何時以及如何發起更新(updating)的那個“被觀察者對象”被命名為Observable。
Observable類用一個标志(flag)來訓示它自己是否改變。對于比較簡單的設計來說,不用flag也是可以的;如果有變化,就通知所有的觀察者。如果用Flag的話,你可以使通知的時間延遲,并且由你來決定隻在合适的時候通知觀察者。但是,請注意,控制flag狀态的方法是受保護的(protected),也就是說,隻有(Observable類的)派生類可以決定哪些東西可以構成一個變化(constitues a change),而不是由Observer派生類的最終使用者來決定。
大多數工作是在notifyObservers()這個方法裡完成的。如果沒有将Flag置為“已改變”,那notifyObservers()什麼也不做;否則,它先清除flag的“已改變”狀态,進而避免重複調用notifyObservers()的時候浪費時間。This is done before notifying the observers in case the calls to update() do anything that causes a change back to this Observable object.
然後notifyObservers()方法就周遊它所儲存的觀察者序列
,并且調用每個觀察者的update()成員函數。
初看起來,似乎可以用一個普通的Observable對象來管理更新。但是實際上辦不到;為了達到這個效果,你必須繼承Observable類,并且在派生類的代碼裡調用setChanged()方法。它就是用來将flag置為“已改變”的那個成員函數,這麼一來,當你調用notifyObservers()的時候所有的觀察者都會被準确無誤的通知道。在什麼地方調用setChanged(),這取決于你程式的邏輯結構。
觀察花朵
下面是observer模式的一個例子。
//: observer:ObservedFlower.java
// Demonstration of "observer" pattern.
package observer;
import java.util.*;
import junit.framework.*;
class Flower {
private boolean isOpen;
private OpenNotifier oNotify =
new OpenNotifier();
private CloseNotifier cNotify =
new CloseNotifier();
public Flower() { isOpen = false; }
public void open() { // Opens its petals
isOpen = true;
oNotify.notifyObservers();
cNotify.open();
}
public void close() { // Closes its petals
isOpen = false;
cNotify.notifyObservers();
oNotify.close();
}
public Observable opening() { return oNotify; }
public Observable closing() { return cNotify; }
private class OpenNotifier extends Observable {
private boolean alreadyOpen = false;
public void notifyObservers() {
if(isOpen && !alreadyOpen) {
setChanged();
super.notifyObservers();
alreadyOpen = true;
}
}
public void close() { alreadyOpen = false; }
}
private class CloseNotifier extends Observable{
private boolean alreadyClosed = false;
public void notifyObservers() {
if(!isOpen && !alreadyClosed) {
setChanged();
super.notifyObservers();
alreadyClosed = true;
}
}
public void open() { alreadyClosed = false; }
}
}
class Bee {
private String name;
private OpenObserver openObsrv =
new OpenObserver();
private CloseObserver closeObsrv =
new CloseObserver();
public Bee(String nm) { name = nm; }
// An inner class for observing openings:
private class OpenObserver implements Observer{
public void update(Observable ob, Object a) {
System.out.println("Bee " + name
+ "'s breakfast time!");
}
}
// Another inner class for closings:
private class CloseObserver implements Observer{
public void update(Observable ob, Object a) {
System.out.println("Bee " + name
+ "'s bed time!");
}
}
public Observer openObserver() {
return openObsrv;
}
public Observer closeObserver() {
return closeObsrv;
}
}
class Hummingbird {
private String name;
private OpenObserver openObsrv =
new OpenObserver();
private CloseObserver closeObsrv =
new CloseObserver();
public Hummingbird(String nm) { name = nm; }
private class OpenObserver implements Observer{
public void update(Observable ob, Object a) {
System.out.println("Hummingbird " + name
+ "'s breakfast time!");
}
}
private class CloseObserver implements Observer{
public void update(Observable ob, Object a) {
System.out.println("Hummingbird " + name
+ "'s bed time!");
}
}
public Observer openObserver() {
return openObsrv;
}
public Observer closeObserver() {
return closeObsrv;
}
}
public class ObservedFlower extends TestCase {
Flower f = new Flower();
Bee
ba = new Bee("A"),
bb = new Bee("B");
Hummingbird
ha = new Hummingbird("A"),
hb = new Hummingbird("B");
public void test() {
f.opening().addObserver(ha.openObserver());
f.opening().addObserver(hb.openObserver());
f.opening().addObserver(ba.openObserver());
f.opening().addObserver(bb.openObserver());
f.closing().addObserver(ha.closeObserver());
f.closing().addObserver(hb.closeObserver());
f.closing().addObserver(ba.closeObserver());
f.closing().addObserver(bb.closeObserver());
// Hummingbird B decides to sleep in:
f.opening().deleteObserver(
hb.openObserver());
// A change that interests observers:
f.open();
f.open(); // It's already open, no change.
// Bee A doesn't want to go to bed:
f.closing().deleteObserver(
ba.closeObserver());
f.close();
f.close(); // It's already closed; no change
f.opening().deleteObservers();
f.open();
f.close();
}
public static void main(String args[]) {
junit.textui.TestRunner.run(ObservedFlower.class);
}
} ///:~
(觀察者)感興趣的事件是花朵的張開和關閉(open or close)。因為有内部類 (Inner class) 可以用,是以這兩種事件可以被分開觀察。OpenNotifier 和CloseNotifier都從Observable繼承而來,它們都可以使用setChanged () 方法,都可以(作為Observable的子類)傳遞給需要一個Observable對象的地方。
使用inner class 來定義多個Observer也是非常友善的,對于Bee和Hummingbird(蜂鳥),它們可能需要互相獨立的觀察花朵的張開和關閉。請注意一下,通過使用inner class這種方法,我們實作了一些通過繼承才能得到的好處,而且更進一步,通過inner class你還可以通路外部類的私有資料成員,這是繼承所不能做到的。
在Main()函數裡,你可以看到observer模式所帶來的最大好處:通過向被觀察者(Observables)動态的注冊和解除安裝觀察者對象(Observers),進而在運作時刻改變被觀察者的行為。
研究一下上面那些代碼你就會發現,OpenNotifier和CloseNotifier隻用到了Observable類的基本接口。(這裡似乎作者沒寫完,感覺不連貫)這就意味着你也可以繼承其它完全不同的Observer類;觀察者和花朵之間唯一的聯系就是Observer接口。
關于observers的一個可視化的例子
下面這個例子很像Thinking in Java第二版第14章ColorBoxes那個例子。在螢幕網格上放上方塊(boxes),用随機選出的顔色給這些方塊着色。除了這些,每個方塊都實作了Observer接口并注冊到一個Observable對象。當你單擊某個方塊的時候,所有其它的方塊都會被通知到,因為Observable對象自動調用每一個Observer對象的update()函數。在update()函數内部,方塊對象會檢查一下(位置資訊)看自己是否與被單擊的那個方塊相鄰,如果相鄰它就把自己的顔色改的和它一樣。
//: observer:BoxObserver.java
// Demonstration of Observer pattern using
// Java's built-in observer classes.
package observer;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
// You must inherit a new type of Observable:
class BoxObservable extends Observable {
public void notifyObservers(Object b) {
// Otherwise it won't propagate changes:
setChanged();
super.notifyObservers(b);
}
}
public class BoxObserver extends JFrame {
Observable notifier = new BoxObservable();
public BoxObserver(int grid) {
setTitle("Demonstrates Observer pattern");
Container cp = getContentPane();
cp.setLayout(new GridLayout(grid, grid));
for(int x = 0; x < grid; x++)
for(int y = 0; y < grid; y++)
cp.add(new OCBox(x, y, notifier));
}
public static void main(String[] args) {
int grid = 8;
if(args.length > 0)
grid = Integer.parseInt(args[0]);
JFrame f = new BoxObserver(grid);
f.setSize(500, 400);
f.setVisible(true);
f.setDefaultCloseOperation(EXIT_ON_CLOSE);
}
}
class OCBox extends JPanel implements Observer {
Observable notifier;
int x, y; // Locations in grid
Color cColor = newColor();
static final Color[] colors = {
Color.BLACK, Color.BLUE, Color.CYAN,
Color.DARK_GRAY, Color.GRAY, Color.GREEN,
Color.LIGHT_GRAY, Color.MAGENTA,
Color.ORANGE, Color.PINK, Color.RED,
Color.WHITE, Color.YELLOW
};
static Random rand = new Random();
static final Color newColor() {
return colors[rand.nextInt(colors.length)];
}
OCBox(int x, int y, Observable notifier) {
this.x = x;
this.y = y;
notifier.addObserver(this);
this.notifier = notifier;
addMouseListener(new ML());
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(cColor);
Dimension s = getSize();
g.fillRect(0, 0, s.width, s.height);
}
class ML extends MouseAdapter {
public void mousePressed(MouseEvent e) {
notifier.notifyObservers(OCBox.this);
}
}
public void update(Observable o, Object arg) {
OCBox clicked = (OCBox)arg;
if(nextTo(clicked)) {
cColor = clicked.cColor;
repaint();
}
}
private final boolean nextTo(OCBox b) {
return Math.abs(x - b.x) <= 1 &&
Math.abs(y - b.y) <= 1;
}
} ///:~
剛開始看Observable的聯機幫助的時候,有一點可能會令你比較困惑,從文檔上看似乎你可以使用一個普通的Observable對象來管理更新(updates)。但是實際上這麼做不行;你可以試一下――更改BoxObserver類,(直接)建立一個Observable類而不是BoxObservable類,然後看看會發生什麼:實際上,什麼也不會發生。為了讓Update生效,你必須得先繼承Observable類,然後在派生類的代碼裡調用setChanged () 方法。這個方法設定“changed”标志(flag),這就意味着當你調用notifyObservers()的時候,所有的觀察者(observers)都會無一例外的被通知到。上面的例子裡,setChanged()方法隻是簡單的在notifyObservers()裡被調用,但是你可以用任何(其它)準則來決定什麼時候調用setChanged()方法。
BoxObserver類隻包括一個Observable對象,就是notifier,每次建立新的OCBox對象的時候,它就會和notifier綁定(tied)。在OCBox内部,無論什麼時候你單擊滑鼠都會觸發notifyObservers()方法被調用,然後把被單擊對象當作一個參數傳遞給其它方塊對象,這樣接收這個消息(通過它們自己的update()方法)的方塊對象就知道被單擊的是哪個方塊,進而決定是否需要改變自己。通過組合使用NotifyObservers () 和update () 方法所提供的代碼,你可以實作一些相當複雜的東西。
看起來似乎隻能通過notifyObservers()方法在編譯期(compile time)決定如何通知觀察者對象。但是,如果你更加仔細的看看上面那些代碼,你就會發現,無論是對于BoxObservable還是OCBox,隻有在建立Observable對象的地方你才會意識到你在和BoxObservable打交道,建立完成之後,所有的工作都是通過Observable所提供的基本接口來完成的。這就意味着,如果你希望改變notification的工作狀态(notification behavior),你可以通過繼承其它的Observable類,并且在運作時刻交換它們來實作。
Mediator
Sweep coupling under the rug, how is this different from MVC
MVC has distinct model and view; mediator could be anything. MVC a flavor of mediator
練習
1. 給出一個最簡單的Observer-Observable設計。隻需要寫出兩個類所必需的(minimum)那些方法即可。然後建立一個Observable對象和多個Observers對象,并用Observable對象來更新(update)Observers對象。
2. 寫一個非常簡化的Observer系統,在你的Observable類内部使用java.util.Timer,用以産生需要報告給Observers的事件。用内部類的方法建立幾個不同的Observer對象,并把它們注冊到Observable對象,當Timer事件觸發時,Observers必須得被通知到。
3. 改寫BoxObserver.java,把它變成一個簡單的遊戲。對于被單擊的那個方塊周圍的其它任一方塊,如果它屬于某個相連的同色區域,那就把所有屬于這個同色區域的方塊都改為和被單擊方塊同樣的顔色(這句累死了)。你可以通過改變遊戲的配置,把它變成多玩家相競争的遊戲,或者你可以記錄單個玩家完成遊戲(指所有方塊變為同一顔色)所用的步數。你還可以限制玩家隻能以它第一步選擇的那種顔色作為最終結束的顔色。
目錄