Статьи

Шаблон проектирования наблюдателя в Java — пример учебного пособия

Шаблон наблюдателя является одним из шаблонов поведенческого проектирования . Шаблон проектирования Observer полезен, когда вы интересуетесь состоянием объекта и хотите получать уведомления при любых изменениях. В шаблоне наблюдателя объект, который наблюдает за состоянием другого объекта, называется наблюдателем, а объект, который отслеживается, называется субъектом . Согласно GoF, намерение наблюдателя — это;

Определите зависимость «один ко многим» между объектами, чтобы при изменении состояния одного объекта все его иждивенцы уведомлялись и обновлялись автоматически.

Субъект содержит список наблюдателей, которые уведомляют о любых изменениях своего состояния, поэтому он должен предоставить методы, с помощью которых наблюдатели могут регистрироваться и отменять регистрацию. Субъект также содержит метод для уведомления всех наблюдателей о любых изменениях и может либо отправить обновление при уведомлении наблюдателя, либо может предоставить другой метод для получения обновления.

У Обозревателя должен быть метод для установки объекта для наблюдения и другой метод, который будет использоваться Субъектом для уведомления их о любых обновлениях.

Java предоставляет встроенную платформу для реализации шаблона Observer через класс java.util.Observable и интерфейс java.util.Observer . Однако он не очень широко используется, потому что реализация действительно проста, и в большинстве случаев мы не хотим заканчивать расширением класса только для реализации шаблона Observer, поскольку java не обеспечивает множественное наследование в классах.

Служба сообщений Java (JMS) использует шаблон Observer вместе с шаблоном Mediator, чтобы приложения могли подписываться и публиковать данные в других приложениях.

Платформы Model-View-Controller (MVC) также используют шаблон Observer, где Model — это субъект, а Views — наблюдатели, которые могут зарегистрироваться, чтобы получать уведомления о любых изменениях в модели.

Пример Наблюдателя

В нашем примере мы реализуем простую тему, и наблюдатели могут зарегистрироваться в этой теме. Всякий раз, когда любое новое сообщение будет опубликовано в теме, все наблюдатели реестров будут уведомлены, и они могут использовать сообщение.

Основываясь на требованиях субъекта, здесь представлен базовый интерфейс субъекта, который определяет методы контракта, которые должны быть реализованы любым конкретным субъектом.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
package com.journaldev.design.observer;
 
public interface Subject {
 
    //methods to register and unregister observers
    public void register(Observer obj);
    public void unregister(Observer obj);
 
    //method to notify observers of change
    public void notifyObservers();
 
    //method to get updates from subject
    public Object getUpdate(Observer obj);
 
}

Далее мы создадим контракт для Обозревателя, будет метод для прикрепления Субъекта к наблюдателю и другой метод, который Субъект будет использовать для уведомления о любых изменениях.

01
02
03
04
05
06
07
08
09
10
package com.journaldev.design.observer;
 
public interface Observer {
 
    //method to update the observer, used by subject
    public void update();
 
    //attach with subject to observe
    public void setSubject(Subject sub);
}

Теперь наш контракт готов, давайте приступим к конкретной реализации нашей темы.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.journaldev.design.observer;
 
import java.util.ArrayList;
import java.util.List;
 
public class MyTopic implements Subject {
 
    private List<Observer> observers;
    private String message;
    private boolean changed;
    private final Object MUTEX= new Object();
 
    public MyTopic(){
        this.observers=new ArrayList<>();
    }
    @Override
    public void register(Observer obj) {
        if(obj == null) throw new NullPointerException("Null Observer");
        if(!observers.contains(obj)) observers.add(obj);
    }
 
    @Override
    public void unregister(Observer obj) {
        observers.remove(obj);
    }
 
    @Override
    public void notifyObservers() {
        List<Observer> observersLocal = null;
        //synchronization is used to make sure any observer registered after message is received is not notified
        synchronized (MUTEX) {
            if (!changed)
                return;
            observersLocal = new ArrayList<>(this.observers);
            this.changed=false;
        }
        for (Observer obj : observersLocal) {
            obj.update();
        }
 
    }
 
    @Override
    public Object getUpdate(Observer obj) {
        return this.message;
    }
 
    //method to post message to the topic
    public void postMessage(String msg){
        System.out.println("Message Posted to Topic:"+msg);
        this.message=msg;
        this.changed=true;
        notifyObservers();
    }
 
}

Реализация метода для регистрации и отмены регистрации наблюдателя очень проста, дополнительный метод — postMessage (), который будет использоваться клиентским приложением для публикации сообщения String в теме. Обратите внимание на логическую переменную, которая отслеживает изменение состояния темы и используется при уведомлении наблюдателей. Эта переменная необходима для того, чтобы в случае отсутствия обновления и вызова метода notifyObservers () наблюдатели не отправляли ложные уведомления.

Также обратите внимание на использование синхронизации в методе notifyObservers (), чтобы уведомление отправлялось только зарегистрированным наблюдателям до публикации сообщения в теме.

Вот реализация Observers, которая будет следить за предметом.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.journaldev.design.observer;
 
public class MyTopicSubscriber implements Observer {
 
    private String name;
    private Subject topic;
 
    public MyTopicSubscriber(String nm){
        this.name=nm;
    }
    @Override
    public void update() {
        String msg = (String) topic.getUpdate(this);
        if(msg == null){
            System.out.println(name+":: No new message");
        }else
        System.out.println(name+":: Consuming message::"+msg);
    }
 
    @Override
    public void setSubject(Subject sub) {
        this.topic=sub;
    }
 
}

Обратите внимание на реализацию метода update (), где он вызывает метод Subject getUpdate (), чтобы получить сообщение для потребления. Мы могли бы избежать этого вызова, передав сообщение в качестве аргумента методу update () .

Вот простая тестовая программа для использования нашей тематической реализации.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.journaldev.design.observer;
 
public class ObserverPatternTest {
 
    public static void main(String[] args) {
        //create subject
        MyTopic topic = new MyTopic();
 
        //create observers
        Observer obj1 = new MyTopicSubscriber("Obj1");
        Observer obj2 = new MyTopicSubscriber("Obj2");
        Observer obj3 = new MyTopicSubscriber("Obj3");
 
        //register observers to the subject
        topic.register(obj1);
        topic.register(obj2);
        topic.register(obj3);
 
        //attach observer to subject
        obj1.setSubject(topic);
        obj2.setSubject(topic);
        obj3.setSubject(topic);
 
        //check if any update is available
        obj1.update();
 
        //now send message to subject
        topic.postMessage("New Message");
    }
 
}

Когда мы запускаем вышеуказанную программу, мы получаем следующий вывод.

1
2
3
4
5
Obj1:: No new message
Message Posted to Topic:New Message
Obj1:: Consuming message::New Message
Obj2:: Consuming message::New Message
Obj3:: Consuming message::New Message

Диаграмма Класса Наблюдателя

Наблюдатель-модель

Шаблон наблюдателя также называется шаблоном публикации-подписки. Некоторые из его реализаций;

  • java.util.EventListener в Swing
  • javax.servlet.http.HttpSessionBindingListener
  • javax.servlet.http.HttpSessionAttributeListener

Это все для шаблона Observer в Java, надеюсь, вам понравилось. Поделитесь своей любовью с комментариями и поделитесь ею с другими.