Статьи

Пример шаблона наблюдателя

Эта статья является частью нашего курса Академии под названием « Шаблоны проектирования Java» .

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

1. Наблюдательский паттерн

Sports Lobby — это фантастический спортивный сайт для любителей спорта. Они охватывают практически все виды спорта и предоставляют последние новости, информацию, запланированные даты матчей, информацию о конкретном игроке или команде. Теперь они планируют предоставлять комментарии в реальном времени или оценки матчей в виде SMS-сервиса, но только для своих премиум-пользователей. Их цель — отправить SMS с реальным счетом, ситуацией матча и важными событиями через короткие промежутки времени. Как пользователь, вы должны подписаться на пакет, и когда будет живое совпадение, вы получите SMS на живой комментарий. На сайте также есть возможность отписаться от пакета, когда вы захотите.

Как разработчик, спортивное лобби попросило вас предоставить им эту новую функцию. Журналисты «Спортивного лобби» будут сидеть в поле для комментариев в матче, и они обновят живой комментарий к объекту комментария. Ваша задача как разработчика — предоставить комментарий зарегистрированным пользователям, извлекая его из объекта комментария, когда он станет доступен. При наличии обновления система должна обновить подписанных пользователей, отправив им SMS.

Эта ситуация ясно показывает сопоставление «один ко многим» между совпадением и пользователями, поскольку может быть много пользователей, подписавшихся на одно совпадение. Шаблон проектирования Observer лучше всего подходит для этой ситуации, давайте посмотрим на этот шаблон, а затем создадим функцию для спортивного лобби.

2. Что такое шаблон наблюдателя

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

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

Другой способ понять шаблон наблюдателя — это способ работы отношений издатель-подписчик. Предположим, например, что вы подписались на журнал для своего любимого спортивного или модного журнала. Всякий раз, когда публикуется новый номер, он доставляется вам. Если вы откажетесь от него, когда вам больше не нужен журнал, он не будет доставлен вам. Но издатель продолжает работать, как и раньше, поскольку есть другие люди, которые также подписаны на этот конкретный журнал.

Диаграмма классов 1

фигура 1

В шаблоне Observer есть четыре участника:

  1. Тема, которая используется для регистрации наблюдателей. Объекты используют этот интерфейс для регистрации в качестве наблюдателей, а также для удаления себя из числа наблюдателей.
  2. Observer, определяет интерфейс обновления для объектов, которые должны быть уведомлены об изменениях в теме. Все наблюдатели должны реализовать интерфейс Observer. Этот интерфейс имеет метод update() , который вызывается при изменении состояния субъекта.
  3. ConcreteSubject, хранит состояние интереса для объектов ConcreteObserver. Он отправляет уведомление своим наблюдателям, когда его состояние изменяется. Конкретный субъект всегда реализует интерфейс субъекта. Метод notifyObservers() используется для обновления всех текущих наблюдателей при каждом изменении состояния.
  4. ConcreateObserver, поддерживает ссылку на объект ConcreteSubject и реализует интерфейс Observer. Каждый наблюдатель регистрируется с конкретной темой, чтобы получать обновления.

3. Реализация шаблона наблюдателя

Давайте посмотрим, как использовать шаблон наблюдателя при разработке функции для спортивного лобби. Кто-то обновит объект конкретного субъекта, а ваша задача — обновить состояние объекта, зарегистрированного в конкретном объекте субъекта. Таким образом, всякий раз, когда происходит изменение состояния объекта конкретного субъекта, все его зависимые объекты должны получать уведомления и затем обновляться.

Диаграмма классов 2

фигура 2

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

1
2
3
4
5
6
7
8
9
package com.javacodegeeks.patterns.observerpattern;
 
public interface Subject {
 
    public void subscribeObserver(Observer observer);
    public void unSubscribeObserver(Observer observer);
    public void notifyObservers();
    public String subjectDetails();
}

Три ключевых метода в интерфейсе Subject :

  1. subscribeObserver , который используется для подписки наблюдателей, или мы можем сказать, зарегистрировать наблюдателей, чтобы в случае изменения состояния субъекта все эти наблюдатели получали уведомление.
  2. unSubscribeObserver , который используется для unSubscribeObserver подписки наблюдателей, чтобы в случае изменения состояния субъекта этот отписанный наблюдатель не получал уведомление.
  3. notifyObservers , этот метод уведомляет зарегистрированных наблюдателей, когда происходит изменение состояния субъекта.

И, по желанию, есть еще один метод subjectDetails() , это тривиальный метод, соответствующий вашим потребностям. Здесь его работа состоит в том, чтобы вернуть детали предмета.

Теперь давайте посмотрим интерфейс Observer .

1
2
3
4
5
6
7
8
package com.javacodegeeks.patterns.observerpattern;
 
public interface Observer {
 
    public void update(String desc);
    public void subscribe();
    public void unSubscribe();
}
  1. update(String desc) , метод вызывается субъектом на наблюдателе, чтобы уведомить его об изменении состояния субъекта.
  2. subscribe() , метод используется, чтобы подписаться с темой.
  3. unsubscribe() , метод используется, чтобы отписаться от темы.
1
2
3
4
5
6
package com.javacodegeeks.patterns.observerpattern;
 
public interface Commentary {
 
    public void setDesc(String desc);
}

Приведенный выше интерфейс используется журналистами для обновления живого комментария к объекту комментария. Это необязательный интерфейс, просто для того, чтобы следовать принципу кода для интерфейса , не связанному с шаблоном Observer Вы должны применять принципы упс, а также шаблоны проектирования, где это применимо. Интерфейс содержит только один метод, который используется для изменения состояния конкретного предметного объекта.

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
package com.javacodegeeks.patterns.observerpattern;
 
import java.util.List;
 
public class CommentaryObject implements Subject,Commentary{
 
    private final List<Observer>observers;
    private String desc;
    private final String subjectDetails;
 
    public CommentaryObject(List<Observer>observers,String subjectDetails){
        this.observers = observers;
        this.subjectDetails = subjectDetails;
    }
    @Override
    public void subscribeObserver(Observer observer) {
        observers.add(observer);
    }
 
    @Override
    public void unSubscribeObserver(Observer observer) {
        int index = observers.indexOf(observer);
        observers.remove(index);
 
    }
 
    @Override
    public void notifyObservers() {
        System.out.println();
        for(Observer observer : observers){
            observer.update(desc);
        }
 
    }
 
    @Override
    public void setDesc(String desc) {
        this.desc = desc;
        notifyObservers();
    }
    @Override
    public String subjectDetails() {
        return subjectDetails;
    }
 
}

Вышеуказанный класс работает как конкретный субъект, который реализует интерфейс Subject и обеспечивает его реализацию. Он также хранит ссылку на зарегистрированных наблюдателей.

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
package com.javacodegeeks.patterns.observerpattern;
 
public class SMSUsers implements Observer{
 
    private final Subject subject;
    private String desc;
    private String userInfo;
 
    public SMSUsers(Subject subject,String userInfo){
        if(subject==null){
            throw new IllegalArgumentException("No Publisher found.");
        }
        this.subject = subject;
        this.userInfo = userInfo;
    }
 
    @Override
    public void update(String desc) {
        this.desc = desc;
        display();
    }
 
    private void display(){
        System.out.println("["+userInfo+"]: "+desc);
    }
 
    @Override
    public void subscribe() {
        System.out.println("Subscribing "+userInfo+" to "+subject.subjectDetails()+" ...");
        this.subject.subscribeObserver(this);
        System.out.println("Subscribed successfully.");
    }
 
    @Override
    public void unSubscribe() {
        System.out.println("Unsubscribing "+userInfo+" to "+subject.subjectDetails()+" ...");
        this.subject.unSubscribeObserver(this);
        System.out.println("Unsubscribed successfully.");
    }
 
}

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

Теперь давайте проверим пример.

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
package com.javacodegeeks.patterns.observerpattern;
 
import java.util.ArrayList;
 
public class TestObserver {
 
    public static void main(String[] args) {
        Subject subject = new CommentaryObject(new ArrayList<Observer>(), "Soccer Match [2014AUG24]");
        Observer observer = new SMSUsers(subject, "Adam Warner [New York]");
        observer.subscribe();
 
        System.out.println();
 
        Observer observer2 = new SMSUsers(subject, "Tim Ronney [London]");
        observer2.subscribe();
 
        Commentary cObject = ((Commentary)subject);
        cObject.setDesc("Welcome to live Soccer match");
        cObject.setDesc("Current score 0-0");
 
        System.out.println();
 
        observer2.unSubscribe();
 
        System.out.println();
 
        cObject.setDesc("It's a goal!!");
        cObject.setDesc("Current score 1-0");
 
        System.out.println();
 
        Observer observer3 = new SMSUsers(subject, "Marrie [Paris]");
        observer3.subscribe();
 
        System.out.println();
 
        cObject.setDesc("It's another goal!!");
        cObject.setDesc("Half-time score 2-0");
 
    }
 
}

Приведенный выше пример выдаст следующий вывод:

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
Subscribing Adam Warner [New York] to Soccer Match [2014AUG24] ...
Subscribed successfully.
 
Subscribing Tim Ronney [London] to Soccer Match [2014AUG24] ...
Subscribed successfully.
 
[Adam Warner [New York]]: Welcome to live Soccer match
[Tim Ronney [London]]: Welcome to live Soccer match
 
[Adam Warner [New York]]: Current score 0-0
[Tim Ronney [London]]: Current score 0-0
 
Unsubscribing Tim Ronney [London] to Soccer Match [2014AUG24] ...
Unsubscribed successfully.
 
[Adam Warner [New York]]: It's a goal!!
 
[Adam Warner [New York]]: Current score 1-0
 
Subscribing Marrie [Paris] to Soccer Match [2014AUG24] ...
Subscribed successfully.
 
[Adam Warner [New York]]: It's another goal!!
[Marrie [Paris]]: It's another goal!!
 
[Adam Warner [New York]]: Half-time score 2-0
[Marrie [Paris]]: Half-time score 2-0

Как видите, сначала два пользователя подписались на футбольный матч и начали получать комментарии. Но позже один пользователь отменил подписку, поэтому пользователь больше не получил комментарий. Затем другой пользователь подписался и начал получать комментарий.

Все это происходит динамически, без изменения существующего кода, и не только это, предположим, если компания хочет транслировать комментарии по электронной почте или любая другая фирма хочет сотрудничать с этой компанией для трансляции комментария. Все, что вам нужно сделать, — это создать два новых класса, таких как UserEmail и ColCompany и сделать их наблюдателями за объектом, реализовав интерфейс Observer . Поскольку Subject знает, что это наблюдатель, он предоставит обновление.

4. Встроенный в Java шаблон Observer

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

Давайте попробуем реализовать приведенный выше пример, используя встроенный в Java шаблон Observer.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.javacodegeeks.patterns.observerpattern;
 
import java.util.Observable;
 
public class CommentaryObjectObservable extends Observable implements Commentary {
    private String desc;
    private final String subjectDetails;
 
    public CommentaryObjectObservable(String subjectDetails){
        this.subjectDetails = subjectDetails;
    }
 
    @Override
    public void setDesc(String desc) {
        this.desc = desc;
        setChanged();
        notifyObservers(desc);
    }
 
    public String subjectDetails() {
        return subjectDetails;
    }
}

На этот раз мы расширяем класс Observable чтобы сделать наш класс субъектом, и обратите внимание, что вышеприведенный класс не содержит ссылок на наблюдателей, он обрабатывается родительским классом, то есть классом Observable . Однако мы объявили метод setDesc для изменения состояния объекта, как это было сделано в предыдущем примере. Метод setChanged — это метод из высшего класса, который используется для установки измененного флага в значение true. Метод notifyObservers уведомляет всех своих наблюдателей, а затем вызывает метод clearChanged чтобы указать, что этот объект больше не изменился. У каждого наблюдателя есть свой метод обновления, вызываемый с двумя аргументами: observable объект и аргумент arg .

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
package com.javacodegeeks.patterns.observerpattern;
 
import java.util.Observable;
 
public class SMSUsersObserver implements java.util.Observer{
 
    private String desc;
    private final String userInfo;
    private final Observable observable;
 
    public SMSUsersObserver(Observable observable,String userInfo){
        this.observable = observable;
        this.userInfo = userInfo;
    }
 
    public void subscribe() {
        System.out.println("Subscribing "+userInfo+" to "+((CommentaryObjectObservable)(observable)).subjectDetails()+" ...");
        this.observable.addObserver(this);
        System.out.println("Subscribed successfully.");
    }
 
    public void unSubscribe() {
        System.out.println("Unsubscribing "+userInfo+" to "+((CommentaryObjectObservable)(observable)).subjectDetails()+" ...");
        this.observable.deleteObserver(this);
        System.out.println("Unsubscribed successfully.");
    }
 
    @Override
    public void update(Observable o, Object arg) {
            desc = (String)arg;
            display();
    }
 
    private void display(){
        System.out.println("["+userInfo+"]: "+desc);
    }
 
}

Давайте обсудим некоторые из ключевых методов.

Приведенный выше класс реализует интерфейс Observer который имеет одно ключевое update метода, которое вызывается, когда субъект вызывает метод notifyObservers . Метод update принимает в качестве параметров объект Observable и arg .

Метод addObserver используется для регистрации наблюдателя для субъекта, а метод deleteObserver используется для удаления наблюдателя из списка субъекта.

Давайте проверим этот пример.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
package com.javacodegeeks.patterns.observerpattern;
 
public class Test {
 
    public static void main(String[] args) {
        CommentaryObjectObservable obj = new CommentaryObjectObservable("Soccer Match [2014AUG24]");
        SMSUsersObserver observer = new SMSUsersObserver(obj, "Adam Warner [New York]");
        SMSUsersObserver observer2 = new SMSUsersObserver(obj,"Tim Ronney [London]");
        observer.subscribe();
        observer2.subscribe();
        System.out.println("------------------------------------------------------");
        obj.setDesc("Welcome to live Soccer match");
        obj.setDesc("Current score 0-0");
 
        observer.unSubscribe();
 
        obj.setDesc("It's a goal!!");
        obj.setDesc("Current score 1-0");
    }
}

Приведенный выше пример выдаст следующий вывод:

01
02
03
04
05
06
07
08
09
10
11
12
13
Subscribing Adam Warner [New York] to Soccer Match [2014AUG24] ...
Subscribed successfully.
Subscribing Tim Ronney [London] to Soccer Match [2014AUG24] ...
Subscribed successfully.
------------------------------------------------------
[Tim Ronney [London]]: Welcome to live Soccer match
[Adam Warner [New York]]: Welcome to live Soccer match
[Tim Ronney [London]]: Current score 0-0
[Adam Warner [New York]]: Current score 0-0
Unsubscribing Adam Warner [New York] to Soccer Match [2014AUG24] ...
Unsubscribed successfully.
[Tim Ronney [London]]: It's a goal!!
[Tim Ronney [London]]: Current score 1-0

Вышеупомянутый класс создает предмет и двух наблюдателей. Метод subscribe observer добавляется в список наблюдателей. Затем setDesc изменяет состояние субъекта, который вызывает метод setChanged чтобы установить флаг изменения в true, и уведомляет наблюдателей. В результате вызывается метод update наблюдателя, который внутренне классифицирует метод отображения для отображения результата. Позже одному из наблюдателей unsubscribe d, т.е. он удаляется из списка наблюдателей. Из-за чего более поздние комментарии к нему не обновлялись.

Java предоставляет встроенные средства для паттерна Observer, но у него есть свои недостатки. Observable — это класс, вы должны создать его подкласс. Это означает, что вы не можете добавить поведение Observable к существующему классу, который уже расширяет другой суперкласс. Это ограничивает потенциал повторного использования. Вы даже не можете создать свою собственную реализацию, которая хорошо работает со встроенным в Java Observer API. Некоторые из методов в Observable API защищены. Это означает, что вы не можете вызывать такие методы, как setChange если у вас нет подкласса Observable . И вы даже не можете создать экземпляр класса Observable и составить его с вашими собственными объектами, вы должны создать подкласс. Этот дизайн нарушает принцип дизайна «отдать предпочтение композиции по наследству» .

5. Когда использовать шаблон наблюдателя

Используйте шаблон Observer в любой из следующих ситуаций:

  1. Когда абстракция имеет два аспекта, один зависит от другого. Инкапсуляция этих аспектов в отдельных объектах позволяет варьировать и повторно использовать их независимо.
  2. Когда изменение одного объекта требует изменения других, и вы не знаете, сколько объектов нужно изменить.
  3. Когда объект должен иметь возможность уведомлять другие объекты, не делая предположений о том, кто эти объекты. Другими словами, вы не хотите, чтобы эти объекты были тесно связаны.

6. Загрузите исходный код

Это был урок по Обозревателю. Вы можете скачать исходный код здесь: ObserverPattern — Урок 7