Статьи

Реализация паттерна Observer и неграмотное программирование

Этот пост является экспериментом по документированию кода Java с использованием (в моем случае) неграмотного программирования . Исходный код Java находится в шаблоне Observer, а код Perl, который преобразовал код Java, находится в Simpleminded Java + Javadoc в HTML Converter .

Шаблон 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