Observer
January 5, 2022你打算開發多人連線程式,每個連線後的客戶端,都會建立一個 Client
物件封裝客戶端資訊,然後加入 ClientQueue
管理:
record Client(String ip, String name) {}
class ClientQueue {
private List<Client> clients = new LinkedList<>();
void add(Client client) {
clients.add(client);
}
void remove(Client client) {
clients.remove(client);
}
....
}
現在你想在 ClientQueue
新增、移除 Client
實例時執行一些任務,於是你在 add
、remove
增加了相關的程式碼,之後別的同事也想在新增、移除 Client
實例時執行一些任務,於是他也在 add
、remove
增加了相關的程式碼,然後又有人想在新增、移除 Client
實例時執行一些任務…唉…等等…
你在看我嗎?
看來很多人都會 ClientQueue
新增、移除 Client
的時機感興趣,只不過做的事情各不相同,那就跟 ClientQueue
登記吧!ClientQueue
會在新增、移除發生時通知你,想做什麼就你家的事!
既然要能通知,就要有個統一的聯絡方式:
import java.util.EventObject;
interface ClientListener {
void clientAdded(EventObject event);
void clientRemoved(EventObject event);
}
然後 ClientQueue
提供登記與移除通知的方法:
class ClientQueue {
private List<Client> clients = new LinkedList<>();
private List<ClientListener> listeners = new LinkedList<>();
void addClientListener(ClientListener listener) {
listeners.add(listener);
}
void removeClientListener(ClientListener listener) {
listeners.remove(listener);
}
void notifyAdded(Client client) {
for(var listener : listeners) {
listener.clientAdded(new EventObject(client));
}
}
void notifyRemoved(Client client) {
for(var listener : listeners) {
listener.clientRemoved(new EventObject(client));
}
}
void add(Client client) {
clients.add(client);
notifyAdded(client);
}
void remove(Client client) {
clients.remove(client);
notifyRemoved(client);
}
...
}
對 ClientQueue
的 Client
新增、移除有興趣的,可以實作 ClientListener
,向 ClientQueue
註冊:
class ClientLogger implements ClientListener {
public void clientAdded(EventObject event) {
var client = (Client) event.getSource();
out.println(client.ip() + " added...");
}
public void clientRemoved(EventObject event) {
var client = (Client) event.getSource();
out.println(client.ip() + " removed...");
}
}
public class Main {
public static void main(String[] args) {
var queue = new ClientQueue();
queue.addClientListener(new ClientLogger());
var c1 = new Client("127.0.0.1", "Justin");
var c2 = new Client("192.168.0.2", "Monica");
queue.add(c1);
queue.add(c2);
queue.remove(c1);
queue.remove(c2);
}
}
被觀察的對象
在 Gof 稱這類實現為 Observer 模式,也有人稱為 Publish-subscribe 模式,你可能會說,這不是就是常見的事件處理嗎?沒錯!事件處理基本是 Observer 模式的應用之一,只不過有時候感興趣的時機,不見得是某個物件的狀態變化,可能是某個模組甚至是應用程式生命週期的變化。
就這邊看到的範例來說,其實一開始你想在 ClientQueue
新增、移除 Client
實例時執行一些任務,就在 add
、remove
增加了相關程式碼,這個動作並沒有錯,重點在於之後,持續有人對 add
、remove
感興趣的這個事實引發了重構的動機,發覺了 ClientQueue
可以負責通知,各自實作由感興趣的人自己處理的想法。
如果應用程式本身負有某種生命週期管理的職責,在一開始規劃時,有經驗的開發者可能就會想在某些生命週期變化時,提供事件處理機制,因而一開始就定義各種傾聽器介面,實現 Observer 的概念;然而,有時候一個物件本身經常被觀察這件事,並不是那麼容易察覺。
因為你可能一開始想在某時機做什麼,就直接在物件中新增了一些程式,若干時日後,又想做個什麼,然後很久之後, 又加入了什麼,持續在某年某月某一書為了某時機,直接加入某些程式碼這件事,也可能是你的同事或者你的接班者,物件的職責可能需要重構,可能就是個被觀察者,只是遲遲沒有被察覺罷了。