Observer 模式


您打算開發一個多人連線程式,其中每個連線後的客戶端,都會建立一個Client物件封裝客戶端的相關資訊,並且加入一個 ClientQueue中集中管理。

假設現在程式中其它部份,可能對ClientQueue中Client的加入或移除有興趣,並且希望Client加入或移除時都可以收到通知,以便作一些 處理,那麼您可以作類似以下的設計:
import java.util.*;

class Client {
private String ip;
private String name;
// ... 其它資料...
public Client(String ip, String name) {
this.ip = ip;
this.name = name;
}
void setIp(String ip) { this.ip = ip; }
void setName(String name) { this.name = name; }
String getIp() { return ip; }
String getName() { return name; }
// ... 其它方法...
}

class ClientEvent {
final String ip;
final String name;
ClientEvent(Client client) {
this.ip = client.getIp();
this.name = client.getName();
}
}

interface ClientListener {
void clientAdded(ClientEvent event);
void clientRemoved(ClientEvent event);
}

class ClientQueue {
private List<Client> clients = new LinkedList<Client>();
private List<ClientListener> listeners = new LinkedList<ClientListener>();

void addClientListener(ClientListener listener) {
listeners.add(listener);
}
void removeClientListener(ClientListener listener) {
listeners.remove(listener);
}

void notifyAdded(Client client) {
ClientEvent event = new ClientEvent(client);
for(ClientListener listener : listeners) {
listener.clientAdded(event);
}
}
void notifyRemoved(Client client) {
ClientEvent event = new ClientEvent(client);
for(ClientListener listener : listeners) {
listener.clientRemoved(event);
}
}

void add(Client client) {
clients.add(client);
notifyAdded(client);
}
void remove(Client client) {
clients.remove(client);
notifyRemoved(client);
}

// 還有一些客戶管理佇列的其它職責....
}

對ClientQueue 中Client物件加入、移除有興趣的,可以實作ClientListener介面,並且向ClientQueue註冊,也就是使用 ClientQueue的addClientListener()來加入至ClientQueue之中(或使用removeClientListener ()移除),每當有Client加入或移除時,會呼叫notifyAdded()或notifyRemoved()方法,方法中會以迴圈方式逐一取出當初 訂閱ClientQueue的物件,並呼叫對應的clientAdded()或clientRemoved()方法。

一個使用的例子如下所示:
class ClientLogger implements ClientListener {
public void clientAdded(ClientEvent event) {
System.out.println(event.ip + " added...");
}
public void clientRemoved(ClientEvent event) {
System.out.println(event.ip + " removed...");
}
}

public class Main {
public static void main(String[] args) {
ClientQueue queue = new ClientQueue();
queue.addClientListener(new ClientLogger());
Client c1 = new Client("127.0.0.1", "caterpillar");
Client c2 = new Client("192.168.0.2", "justin");
queue.add(c1);
queue.add(c2);
queue.remove(c1);
queue.remove(c2);
}
}

這是Observer模式的實現,在Observer模式中的主角為主題(subject)與觀察者(observer),主 題可被多個觀 察者訂閱,當主題的狀態發生變化時,它必須通知(notify)所有訂閱它的觀察者,觀察者檢視主題的狀態變化,並作出對應的動作。

以Python來實現這個範例的話:
class Client:
def __init__(self, ip, name):
self.ip = ip
self.name = name

# ... 其它方法...

class ClientEvent:
pass

class ClientQueue:
def __init__(self):
self.clients = []
self.listeners = []

def addClientListener(self, listener):
self.listeners.append(listener)

def removeClientListener(self, listener):
self.listeners.remove(listener)

def notifyAdded(self, client):
event = ClientEvent()
event.ip = client.ip
event.name = client.name
for listener in self.listeners:
listener.clientAdded(event)

def notifyRemoved(self, client):
event = ClientEvent()
event.ip = client.ip
event.name = client.name
for listener in self.listeners:
listener.clientRemoved(event)

def add(self, client):
self.clients.append(client)
self.notifyAdded(client)

def remove(self, client):
self.clients.remove(client)
self.notifyRemoved(client)

# 還有一些客戶管理佇列的其它職責....

class ClientLogger:
def clientAdded(self, event):
print(event.ip + " added...")

def clientRemoved(self, event):
print(event.ip + " removed...")

queue = ClientQueue()
queue.addClientListener(ClientLogger())
c1 = Client("127.0.0.1", "caterpillar")
c2 = Client("192.168.0.2", "justin")
queue.add(c1)
queue.add(c2)
queue.remove(c1)
queue.remove(c2)

以更一般的方式來表示Observer模式:


Subject類中有一個notify()方法,通常是在Subject的狀態發生改變時呼叫它,notify()中會呼叫 Observer的update()方法,通常會先取得Subject的新狀態,然後更新Observer的顯示或行為,這個過程我們可以透過順序圖來表 達:


在Java中支援觀察者模式,要成為觀察者的類必須實作Observer介面,這個介面中定義了一個update()方法,這個方法會被主題物件在通知狀 態變化時呼叫,您必須在這個方法中實作您所想要的對應行為。主題物件會是Observable的子類,在這邊注意兩個重要的方法:setChanged ()與notifyObserver()。setChanged()是用來設定主題物件的狀態已經被改變,而notifyObserver()方法會通知 所要訂閱主題物件的觀察者,呼叫其 update()方法。