Шаблон Observer используется для создания отношений между двумя объектами. Отношения обычно однонаправлены, когда наблюдатель ожидает уведомления от наблюдаемого. Отношения являются беспорядочными, так как наблюдаемому необходимо знать только интерес наблюдателя, а наблюдателю нужно знать только набор и порядок уведомления, которое наблюдатель отправит. Недостатком этой слабости является то, что все уведомления проходят через один метод наблюдателя для дальнейшей отправки.
10 package com.andrewgilmartin.common.observation; 11 12 import java.util.Set; 13 import java.util.concurrent.BlockingQueue; 14 import java.util.concurrent.CopyOnWriteArraySet; 15 import java.util.concurrent.LinkedBlockingQueue; 16
Класс Observation используется только для группировки всех связанных интерфейсов и классов в один файл. В типичной среде разработки каждый интерфейс и класс должны быть в своем собственном файле.
22 public class Observation { 23
Наблюдаемое — это объект, за которым следят. По мере подготовки и последующего внесения изменений наблюдаемые отправят наблюдателям согласие и информационные уведомления. Уведомления обычно являются специализированными классами, которые содержат некоторый контекст об изменениях. Например, если уведомление касается добавления нового инвентаря на склад, то класс уведомления может иметь метод для перечисления нового инвентаря.
32 public interface Observable { 33
Если наблюдателю необходимо получить одобрение до внесения изменений, то уведомление о согласии отправляется наблюдателям. Каждый наблюдатель будет уведомлен, и если какой-либо наблюдатель будет против изменения, он должен вернуть false. Текущая ветка будет использоваться для уведомления наблюдателей, поэтому все наблюдаемые должны ждать согласия всех наблюдателей.
42 boolean consentNotification(Object notice); 43
Информационное уведомление обычно отправляется после изменения. Поскольку изменение уже произошло, уведомления обычно отправляются фоновым потоком асинхронно. Однако порядок уведомлений сохраняется.
50 void informationNotification(Object notice); 51 } 52
Наблюдатель — это объект, который уведомляется наблюдаемым. За пределами классов Observable и Observer нет типизированных отношений. Как упоминалось ранее, уведомления обычно относятся к специализированным классам, где каждый экземпляр уведомления содержит данные, относящиеся к изменению.
59 public interface Observer { 60 61 boolean notice(Observable observable, Object notice); 62 } 63
Реестр является средством установления отношений между наблюдаемым и наблюдателем. Этот интерфейс отличается от Observable, поскольку иногда полезно косвенно зарегистрировать наблюдателя, например, через регистратора.
70 public interface Registry { 71
Порядок наблюдателей не определен.
75 Set<Observer> getObservers(); 76
Добавляет наблюдателя в набор наблюдателей. Возвращает true, если наблюдатель был добавлен.
81 boolean addObserver(Observer observer); 82
Удаляет наблюдателя из набора наблюдателей. Возвращает true, если наблюдатель был среди наблюдателей и был удален.
87 boolean deleteObserver(Observer observer); 88 } 89
Часто между реализациями Observable и Registry очень мало различий, и поэтому эта базовая реализация может широко использоваться любым классом, который хочет, чтобы его наблюдали.
При расширении этого класса обязательно документируйте набор уведомлений, их согласие или информационную роль, а также порядок их размещения.
98 public static class ObservableBase implements Observable, Registry { 99
Управление набором наблюдателей должно быть поточно-ориентированным. Ожидается, что набор будет в основном стабильным в течение срока службы наблюдаемой, поэтому здесь уместна семантика копирования при записи.
105 private final Set<Observer> observers = new CopyOnWriteArraySet<Observer>();
Информационные уведомления будут отправлены в фоновом потоке. Очередь блокировки будет использоваться для координации передачи уведомлений из наблюдаемого в этот фоновый поток.
111 private final BlockingQueue<Object> informationNotices = new LinkedBlockingQueue<Object>(); 112 113 public ObservableBase() {
Эта реализация фонового потока информационных уведомлений довольно проста, поэтому для реализации используется анонимный класс.
119 Thread informationEventsDispatcher = new Thread(new Runnable() { 120 @Override 121 public void run() { 122 try {
Здесь поток ожидает нового уведомления в очереди и затем отправляет его каждому из текущих наблюдателей.
127 for (;;) { 128 Object notice = informationNotices.take(); 129 for (Observer observer : observers) { 130 observer.notice(ObservableBase.this, notice); 131 } 132 } 133 } 134 catch (InterruptedException e) { 135 // empty 136 } 137 } 138 }); 139 informationEventsDispatcher.setDaemon(true); 140 informationEventsDispatcher.start(); 141 } 142 143 @Override 144 public boolean consentNotification(Object notice) {
Как упоминалось ранее, уведомления о согласии выполняются наблюдаемой веткой. Таким образом, как только наблюдатель выступает против изменения, наблюдаемое должно отклонить изменение.
150 for (Observer observer : observers) { 151 if (!observer.notice(this, notice)) { 152 return false; 153 } 154 } 155 return true; 156 } 157 158 @Override 159 public void informationNotification(Object notice) {
Передайте уведомление фоновой ветке.
163 informationNotices.add(notice); 164 } 165 166 @Override 167 public Set<Observer> getObservers() { 168 return observers; 169 } 170 171 @Override 172 public boolean addObserver(Observer observer) { 173 return observers.add(observer); 174 } 175 176 @Override 177 public boolean deleteObserver(Observer observer) { 178 return observers.remove(observer); 179 } 180 } 181
Вот небольшой пример использования интерфейсов наблюдения и классов.
185 public static void main(String... args) throws Exception { 186 187 class Notice { 188 189 private int senderId; 190 private int sequenceNumber; 191 192 public Notice(int senderId, int sequenceNumber) { 193 this.senderId = senderId; 194 this.sequenceNumber = sequenceNumber; 195 } 196 197 public int getSenderId() { 198 return senderId; 199 } 200 201 public int getSequenceNumber() { 202 return sequenceNumber; 203 } 204 } 205
Отправитель наблюдается. Все, что он делает, это отправляет поток уведомлений, состоящий из пар отправителя и порядкового номера.
210 class Sender extends ObservableBase implements Runnable { 211 212 private int senderId; 213 214 public Sender(int id) { 215 this.senderId = id; 216 } 217 218 @Override 219 public void run() { 220 for (int sequenceNumber = 0;; sequenceNumber++) { 221 informationNotification(new Notice(senderId, sequenceNumber)); 222 sleep(); // add some randomness to the processing. 223 } 224 } 225 } 226
Получатель является наблюдателем. Все, что он делает, это печатает факты уведомления. В этом примере все уведомления являются информационными уведомлениями, поэтому возвращаемое значение note () не имеет значения. Однако, как само собой разумеющееся, метод note () всегда должен возвращать true, если он не уверен в последствиях противодействия изменению.
234 class Receiver implements Observer { 235 236 private int receiverId; 237 238 public Receiver(int id) { 239 this.receiverId = id; 240 } 241 242 @Override 243 public boolean notice(Observable observable, Object notice) { 244 if (notice instanceof Notice) { 245 Notice n = (Notice) notice; 246 System.out.printf("notice %d %d %d\n", receiverId, n.getSenderId(), n.getSequenceNumber()); 247 sleep(); // add some randomness to the processing. 248 } 249 return true; 250 } 251 } 252
Создайте несколько отправителей.
256 Sender[] senders = new Sender[5]; 257 for (int i = 0; i < senders.length; i++) { 258 senders[i] = new Sender(i); 259 } 260
Создайте несколько приемников.
264 Receiver[] receivers = new Receiver[3]; 265 for (int i = 0; i < receivers.length; i++) { 266 receivers[i] = new Receiver(i); 267 } 268
Пусть каждый получатель наблюдает за каждым отправителем
272 for (Receiver r : receivers) { 273 for (Sender s : senders) { 274 s.addObserver(r); 275 } 276 } 277
Пуск отправителей
281 for (Sender s : senders) { 282 Thread t = new Thread(s); 283 t.setDaemon(false); 284 t.start(); 285 } 286 } 287
Добавьте немного сна произвольной продолжительности в текущий поток.
291 static void sleep() { 292 try { 293 Thread.sleep(Math.round(Math.random() * 25)); 294 } 295 catch (InterruptedException e) { 296 // empty 297 } 298 } 299 }
Чтобы собрать и запустить это из командной строки, сначала скомпилируйте, используя
303 javac -d /tmp ./src/com/andrewgilmartin/common/observation/Observation
А затем запустить с помощью
308 java -classpath /tmp com.andrewgilmartin.common.observation.Observation
Вы можете показать себе, что события упорядочены, отсортировав выходные данные по идентификатору получателя, и вы увидите, что события расположены в числовом порядке.
314 java -classpath /tmp com.andrewgilmartin.common.observation.Observation | head -20 | sort -k 2 -n -s
317 318 // END