Observer Pattern 有 2 個重要角色「主題」和「觀察者」,觀察者希望主題狀態有變更時會立即被告知。
你可以想像 Blog 上的 Email 訂閱服務(本 Blog 沒有),當你對這個 Blog 有興趣時, 你會用你的 Email 地址去訂閱服務,當有更新時你會立即收到通知,題示你有更新了,請去看看。 當你對 Blog 上千篇一律的內容感到無聊時,你亦可以取消這個訂閱,那以後有更新亦不會再通知你。
看看以下例子應該有助你明白這個模式:
公司要你開發一個即時新聞平台,用戶只需要打開程式就能夠看到即時新聞。
你一開始可能會用 Pull 的方法,每隔一段時間會主動去向主題拿取新的內容:
(NewsTopic.java) download import java.util.HashMap ;
import java.util.Map ;
import java.util.UUID ;
public class NewsTopic {
private final Map < String , String > messages = new HashMap < String , String >();
public Map < String , String > getMessages () {
return messages ;
}
public void addMessage ( String message ) {
// Assign unique ID to each message for determine is it old message.
messages . put ( UUID . randomUUID (). toString (), message );
}
}
(Client.java) download import java.util.Date ;
import java.util.HashSet ;
import java.util.Map ;
import java.util.Map.Entry ;
import java.util.Set ;
import java.util.TimerTask ;
public class Client extends TimerTask {
private final Set < String > displayedIds = new HashSet < String >();
private NewsTopic topic ;
public Client ( NewsTopic topic ) {
this . topic = topic ;
}
public void displayNews () {
Map < String , String > messages = topic . getMessages ();
if (! displayedIds . containsAll ( messages . keySet ())) {
for ( Entry < String , String > entry : messages . entrySet ()) {
if (! displayedIds . contains ( entry . getKey ())) {
System . out . println ( String . format (
"Client %s display message: %s at time: %s" ,
Integer . toHexString ( this . hashCode ()),
entry . getValue (), new Date ()));
displayedIds . add ( entry . getKey ());
}
}
}
}
@Override
public void run () {
displayNews ();
}
}
(Main.java) download import java.util.Random ;
import java.util.Timer ;
public class Main {
public static void main ( String [] args ) {
NewsTopic topic = new NewsTopic ();
Client client1 = new Client ( topic );
Client client2 = new Client ( topic );
Client client3 = new Client ( topic );
Timer timer1 = new Timer ();
timer1 . schedule ( client1 , 50 , 3000 );
Timer timer2 = new Timer ();
timer2 . schedule ( client2 , 50 , 5000 );
Timer timer3 = new Timer ();
timer3 . schedule ( client3 , 50 , 7000 );
Random random = new Random ();
try {
for ( int i = 0 ; i < 3 ; i ++) {
topic . addMessage ( "News " + i );
Thread . sleep ( random . nextInt ( 5000 ));
}
} catch ( InterruptedException e ) {
e . printStackTrace ();
}
timer1 . cancel ();
timer2 . cancel ();
timer3 . cancel ();
}
}
執行結果 Client 109a4c display message: News 0 at time: Fri Mar 30 00:41:23 CST 2012
Client c40c80 display message: News 0 at time: Fri Mar 30 00:41:25 CST 2012
Client c40c80 display message: News 1 at time: Fri Mar 30 00:41:25 CST 2012
Client 109a4c display message: News 2 at time: Fri Mar 30 00:41:26 CST 2012
Client 109a4c display message: News 1 at time: Fri Mar 30 00:41:26 CST 2012
每個 Client 接收到訊息的時間不一。
有客戶抱怨,股票新聞不夠實時,害他掉了錢。公司要你改善一下程式,盡量減少延遲。
現在是 Observer Pattern 大派用場的時候,因為一有更新就會主動通知觀察者,大大減低新聞延遲的問題。
你可能會問,使用舊方法但將間隔縮短不就可以了嗎? 這個方法雖然可以解決延遲的問題,但如果客戶多達 10000 人時,每一個也不停重覆去檢查資料是非常浪費系統資源, CPU 長期高工作量,會拖跨了系統的其他程式。
(Subject.java) download public interface Subject {
public void registerObserver ( Observer observer );
public void unregisterObserver ( Observer observer );
public void notifyObservers ();
}
(Observer.java) download public interface Observer {
public void notify ( String message );
}
(NewsTopic.java) download import java.util.HashSet ;
import java.util.Set ;
public class NewsTopic implements Subject {
private final Set < Observer > obervers = new HashSet < Observer >();
private String message ;
public void pushMessage ( String message ) {
this . message = message ;
notifyObservers ();
}
@Override
public void registerObserver ( Observer observer ) {
obervers . add ( observer );
}
@Override
public void unregisterObserver ( Observer observer ) {
obervers . remove ( observer );
}
@Override
public void notifyObservers () {
for ( Observer observer: obervers ){
observer . notify ( message );
}
}
}
(Client.java) download import java.util.Date ;
public class Client implements Observer {
@Override
public void notify ( String message ) {
System . out . println ( String . format (
"Client %s display message: %s at time: %s" ,
Integer . toHexString ( this . hashCode ()), message , new Date ()));
}
}
(Main.java) download import java.util.Random ;
public class Main {
public static void main ( String [] args ) {
NewsTopic topic = new NewsTopic ();
Client client1 = new Client ();
Client client2 = new Client ();
Client client3 = new Client ();
topic . registerObserver ( client1 );
topic . registerObserver ( client2 );
topic . registerObserver ( client3 );
Random random = new Random ();
try {
for ( int i = 0 ; i < 3 ; i ++) {
topic . pushMessage ( "News " + i );
Thread . sleep ( random . nextInt ( 5000 ));
}
} catch ( InterruptedException e ) {
e . printStackTrace ();
}
}
}
執行結果 Client 10b30a7 display message: News 0 at time: Fri Mar 30 00:54:03 CST 2012
Client ca0b6 display message: News 0 at time: Fri Mar 30 00:54:03 CST 2012
Client 14318bb display message: News 0 at time: Fri Mar 30 00:54:03 CST 2012
Client 10b30a7 display message: News 1 at time: Fri Mar 30 00:54:07 CST 2012
Client ca0b6 display message: News 1 at time: Fri Mar 30 00:54:07 CST 2012
Client 14318bb display message: News 1 at time: Fri Mar 30 00:54:07 CST 2012
Client 10b30a7 display message: News 2 at time: Fri Mar 30 00:54:09 CST 2012
Client ca0b6 display message: News 2 at time: Fri Mar 30 00:54:09 CST 2012
Client 14318bb display message: News 2 at time: Fri Mar 30 00:54:09 CST 2012
現在每一次發佈訊息時,每一個客戶都同時收到,大大減低了延遲和系統負荷。
以下的圖片是描述 Observer Pattern 的 Class Diagram。(From Wiki)