0 added
0 removed
Original
2026-01-01
Modified
2026-02-21
1
<p><a>#База знаний</a></p>
1
<p><a>#База знаний</a></p>
2
<ul><li>22 ноя 2021</li>
2
<ul><li>22 ноя 2021</li>
3
<li>0</li>
3
<li>0</li>
4
</ul><h2>Шаблон "Наблюдатель": расскажите, как там на Марсе</h2>
4
</ul><h2>Шаблон "Наблюдатель": расскажите, как там на Марсе</h2>
5
<p>Исследуем погоду на Марсе с помощью Java и паттерна проектирования "Наблюдатель".</p>
5
<p>Исследуем погоду на Марсе с помощью Java и паттерна проектирования "Наблюдатель".</p>
6
<p>Фулстек-разработчик. Любимый стек: Java + Angular, но в хорошей компании готова писать хоть на языке Ада.</p>
6
<p>Фулстек-разработчик. Любимый стек: Java + Angular, но в хорошей компании готова писать хоть на языке Ада.</p>
7
<p>18 февраля 2021 года в рамках миссии NASA "Марс-2020" на поверхность Красной планеты успешно приземлился ровер Perseverance - "Настойчивость". С тех пор он передаёт на Землю кучу данных о нашем соседе.</p>
7
<p>18 февраля 2021 года в рамках миссии NASA "Марс-2020" на поверхность Красной планеты успешно приземлился ровер Perseverance - "Настойчивость". С тех пор он передаёт на Землю кучу данных о нашем соседе.</p>
8
<p>Представьте, что вас взяли на работу в NASA, а в качестве первого простого задания попросили разобраться с полученными от Perseverance данными.</p>
8
<p>Представьте, что вас взяли на работу в NASA, а в качестве первого простого задания попросили разобраться с полученными от Perseverance данными.</p>
9
<ul><li><a>Постановка задачи</a></li>
9
<ul><li><a>Постановка задачи</a></li>
10
<li><a>Решение 1: простое и очевидное</a></li>
10
<li><a>Решение 1: простое и очевидное</a></li>
11
<li><a>Что такое паттерн "Наблюдатель"</a></li>
11
<li><a>Что такое паттерн "Наблюдатель"</a></li>
12
<li><a>Решение 2: гибкое и универсал</a></li>
12
<li><a>Решение 2: гибкое и универсал</a></li>
13
</ul><ul><li><a>Как ещё можно передавать обновления</a></li>
13
</ul><ul><li><a>Как ещё можно передавать обновления</a></li>
14
<li><a>Решение 3: стандартизированное</a></li>
14
<li><a>Решение 3: стандартизированное</a></li>
15
<li><a>Подытожим</a></li>
15
<li><a>Подытожим</a></li>
16
</ul><p><strong>Техническое задание</strong></p>
16
</ul><p><strong>Техническое задание</strong></p>
17
<p>Разработать программу, которая при получении новых данных от ровера будет по-разному распоряжаться ими:</p>
17
<p>Разработать программу, которая при получении новых данных от ровера будет по-разному распоряжаться ими:</p>
18
<ul><li>температуру на Марсе выведет на большой экран в холле;</li>
18
<ul><li>температуру на Марсе выведет на большой экран в холле;</li>
19
<li>давление на Марсе покажет на экране в лаборатории;</li>
19
<li>давление на Марсе покажет на экране в лаборатории;</li>
20
<li>свежие фотографии поверхности опубликует на сайте NASA.</li>
20
<li>свежие фотографии поверхности опубликует на сайте NASA.</li>
21
</ul><p>Список вариантов обработки данных не окончательный. Нужно иметь возможность быстро подключать новые обработчики и отключать старые.</p>
21
</ul><p>Список вариантов обработки данных не окончательный. Нужно иметь возможность быстро подключать новые обработчики и отключать старые.</p>
22
<p>А вот и сопроводительные документы. Это класс, в котором хранятся актуальные данные от ровера:</p>
22
<p>А вот и сопроводительные документы. Это класс, в котором хранятся актуальные данные от ровера:</p>
23
public class PerseveranceData { private final double temperature; // температура private final double pressure; // давление private final String photo; // фотография (для простоты пусть это будет строка) public PerseveranceData(double temperature, double pressure, String photo) { this.temperature = temperature; this.pressure = pressure; this.photo = photo; } public double getTemperature() { return temperature; } public double getPressure() { return pressure; } public String getPhoto() { return photo; } }<p>И класс-заготовка для обработчика этих данных:</p>
23
public class PerseveranceData { private final double temperature; // температура private final double pressure; // давление private final String photo; // фотография (для простоты пусть это будет строка) public PerseveranceData(double temperature, double pressure, String photo) { this.temperature = temperature; this.pressure = pressure; this.photo = photo; } public double getTemperature() { return temperature; } public double getPressure() { return pressure; } public String getPhoto() { return photo; } }<p>И класс-заготовка для обработчика этих данных:</p>
24
public class Perseverance { private PerseveranceData data; // последние полученные данные public PerseveranceData getData() { return date; } // этот метод вызывается каждый раз при получении новых данных public void onNewData(PerseveranceData newData){ // сюда можно дописать свою обработку } }<p>Первое решение пришло в голову почти сразу.</p>
24
public class Perseverance { private PerseveranceData data; // последние полученные данные public PerseveranceData getData() { return date; } // этот метод вызывается каждый раз при получении новых данных public void onNewData(PerseveranceData newData){ // сюда можно дописать свою обработку } }<p>Первое решение пришло в голову почти сразу.</p>
25
<p>Тут и думать нечего - раз есть метод, который вызывается при получении новых данных, в нём же эти данные и разошлём по нужным представлениям. Только сначала опишем классы для этих представлений.</p>
25
<p>Тут и думать нечего - раз есть метод, который вызывается при получении новых данных, в нём же эти данные и разошлём по нужным представлениям. Только сначала опишем классы для этих представлений.</p>
26
<p>Для вывода температуры:</p>
26
<p>Для вывода температуры:</p>
27
public class TemperatureDisplay { public void update(PerseveranceData data) { System.out.printf("Температура на Марсе - %2.0f градусов по Цельсию %n", data.getTemperature()); } }<p>Для вывода давления:</p>
27
public class TemperatureDisplay { public void update(PerseveranceData data) { System.out.printf("Температура на Марсе - %2.0f градусов по Цельсию %n", data.getTemperature()); } }<p>Для вывода давления:</p>
28
public class PressureDisplay { public void update(PerseveranceData data) { System.out.printf("Давление на Марсе - %3.1f кПа %n", data.getPressure()); } }<p>И для публикации фотографий:</p>
28
public class PressureDisplay { public void update(PerseveranceData data) { System.out.printf("Давление на Марсе - %3.1f кПа %n", data.getPressure()); } }<p>И для публикации фотографий:</p>
29
public class PhotoPublisher { public void update(PerseveranceData data) { System.out.printf("Опубликовано новое фото Марса - %1$s %n", data.getPhoto()); } }<p>А вот и самая очевидная реализация рассылки новых данных:</p>
29
public class PhotoPublisher { public void update(PerseveranceData data) { System.out.printf("Опубликовано новое фото Марса - %1$s %n", data.getPhoto()); } }<p>А вот и самая очевидная реализация рассылки новых данных:</p>
30
public class Perseverance { private PerseveranceData data; public PerseveranceData getData() { return data; } TemperatureDisplay temperatureDisplay = new TemperatureDisplay(); PressureDisplay pressureDisplay = new PressureDisplay(); PhotoPublisher photoPublisher = new PhotoPublisher(); // этот метод вызывается каждый раз при получении новых данных от ровера public void onNewData(PerseveranceData newData) { data = newData; temperatureDisplay.update(data); pressureDisplay.update(data); photoPublisher.update(data); } }<p>Быстро, просто, всё работает, но есть минусы:</p>
30
public class Perseverance { private PerseveranceData data; public PerseveranceData getData() { return data; } TemperatureDisplay temperatureDisplay = new TemperatureDisplay(); PressureDisplay pressureDisplay = new PressureDisplay(); PhotoPublisher photoPublisher = new PhotoPublisher(); // этот метод вызывается каждый раз при получении новых данных от ровера public void onNewData(PerseveranceData newData) { data = newData; temperatureDisplay.update(data); pressureDisplay.update(data); photoPublisher.update(data); } }<p>Быстро, просто, всё работает, но есть минусы:</p>
31
<ul><li>При добавлении нового представления придётся снова менять класс Perseverance.</li>
31
<ul><li>При добавлении нового представления придётся снова менять класс Perseverance.</li>
32
<li>Невозможно отключать представления и добавлять новые прямо во время выполнения программы.</li>
32
<li>Невозможно отключать представления и добавлять новые прямо во время выполнения программы.</li>
33
<li>Так как в Perseverance используются реализации - конкретные классы представлений, а не интерфейсы, то при появлении другой реализации любого представления опять же придётся менять класс ровера.</li>
33
<li>Так как в Perseverance используются реализации - конкретные классы представлений, а не интерфейсы, то при появлении другой реализации любого представления опять же придётся менять класс ровера.</li>
34
</ul><p>Иными словами, решению недостаёт гибкости, а класс марсохода и классы - представления данных слишком сильно связаны между собой. Но есть вариант и поинтереснее - для решения нашей задачи отлично подойдёт паттерн проектирования "Наблюдатель".</p>
34
</ul><p>Иными словами, решению недостаёт гибкости, а класс марсохода и классы - представления данных слишком сильно связаны между собой. Но есть вариант и поинтереснее - для решения нашей задачи отлично подойдёт паттерн проектирования "Наблюдатель".</p>
35
<p>В классической книге "Паттерны объектно-ориентированного проектирования" авторства так называемой Банды четырёх этот шаблон описывается так:</p>
35
<p>В классической книге "Паттерны объектно-ориентированного проектирования" авторства так называемой Банды четырёх этот шаблон описывается так:</p>
36
<p>"Определяет отношение между объектами "один ко многим“, так что при изменении состояния одного объекта все зависимые от него объекты автоматически получают оповещения об изменениях и тоже обновляются".</p>
36
<p>"Определяет отношение между объектами "один ко многим“, так что при изменении состояния одного объекта все зависимые от него объекты автоматически получают оповещения об изменениях и тоже обновляются".</p>
37
<p>В реальной жизни полно примеров использования этого шаблона:</p>
37
<p>В реальной жизни полно примеров использования этого шаблона:</p>
38
<ul><li>подписка на каналы, сообщества и новости друзей в социальных сетях;</li>
38
<ul><li>подписка на каналы, сообщества и новости друзей в социальных сетях;</li>
39
<li>подписка на получение информации о выходе новых серий любимых сериалов в онлайн-кинотеатрах;</li>
39
<li>подписка на получение информации о выходе новых серий любимых сериалов в онлайн-кинотеатрах;</li>
40
<li>подписка на оповещение об изменении цены на приглянувшийся товар в интернет-магазине.</li>
40
<li>подписка на оповещение об изменении цены на приглянувшийся товар в интернет-магазине.</li>
41
</ul>Примеры паттерна "Наблюдатель" в реальной жизни. Изображение: Майя Мальгина для Skillbox Media<p>Ключевое слово здесь - "подписка". Без неё весь этот поток информации превращается в обычный спам.</p>
41
</ul>Примеры паттерна "Наблюдатель" в реальной жизни. Изображение: Майя Мальгина для Skillbox Media<p>Ключевое слово здесь - "подписка". Без неё весь этот поток информации превращается в обычный спам.</p>
42
<p>В паттерне "Наблюдатель" два типа участников: тот (или те), кто генерирует обновления, и те, кому эти обновления приходят. Чтобы получать обновления, нужно сначала попасть в список подписчиков. И наоборот - если отказаться от подписки, обновления приходить перестанут.</p>
42
<p>В паттерне "Наблюдатель" два типа участников: тот (или те), кто генерирует обновления, и те, кому эти обновления приходят. Чтобы получать обновления, нужно сначала попасть в список подписчиков. И наоборот - если отказаться от подписки, обновления приходить перестанут.</p>
43
<p>Обычно участники первого типа называются<strong>Subject</strong>(Субъект), а второго -<strong>Observer</strong>(Наблюдатель). И Subject, и Observer -<strong>интерфейсы</strong>, на базе которых можно писать свои классы-реализации. В этих же классах можно хранить текущие состояния Субъекта и Наблюдателей.</p>
43
<p>Обычно участники первого типа называются<strong>Subject</strong>(Субъект), а второго -<strong>Observer</strong>(Наблюдатель). И Subject, и Observer -<strong>интерфейсы</strong>, на базе которых можно писать свои классы-реализации. В этих же классах можно хранить текущие состояния Субъекта и Наблюдателей.</p>
44
<p>У Субъекта есть методы для подписки, отказа от подписки и оповещения всех своих подписчиков, у Наблюдателя - метод, который вызывается при получении новых данных от Субъекта.</p>
44
<p>У Субъекта есть методы для подписки, отказа от подписки и оповещения всех своих подписчиков, у Наблюдателя - метод, который вызывается при получении новых данных от Субъекта.</p>
45
Диаграмма классов. Инфографика: Екатерина Степанова / Skillbox Media<p>Сначала напишем интерфейсы. Один для Субъекта:</p>
45
Диаграмма классов. Инфографика: Екатерина Степанова / Skillbox Media<p>Сначала напишем интерфейсы. Один для Субъекта:</p>
46
public interface Subject { void registerObserver(Observer observer); void unregisterObserver(Observer observer); void notifyObservers(); }<p>Второй - для Наблюдателей:</p>
46
public interface Subject { void registerObserver(Observer observer); void unregisterObserver(Observer observer); void notifyObservers(); }<p>Второй - для Наблюдателей:</p>
47
public interface Observer { void update(PerseveranceData data); }<p>Теперь перепишем реализацию Perseverance таким образом, чтобы он реализовывал интерфейс Subject:</p>
47
public interface Observer { void update(PerseveranceData data); }<p>Теперь перепишем реализацию Perseverance таким образом, чтобы он реализовывал интерфейс Subject:</p>
48
public class Perseverance implements Subject { private PerseveranceData data; // актуальный список Наблюдателей private Set<Observer> observers = new HashSet<>(); @Override public void registerObserver(Observer observer) { observers.add(observer); } @Override public void unregisterObserver(Observer observer) { observers.remove(observer); } @Override public void notifyObservers() { for (Observer observer : observers) observer.update(data); } public PerseveranceData getData() { return data; } // этот метод вызывается каждый раз при получении новых данных от ровера public void onNewData(PerseveranceData newData) { this.data = newData; notifyObservers(); } }<p>Perseverance хранит список Наблюдателей в переменной observers. Это множество (Set), так как в этом списке не допускаются дубликаты (представления одного типа), а также нам не важен порядок оповещения Наблюдателей.</p>
48
public class Perseverance implements Subject { private PerseveranceData data; // актуальный список Наблюдателей private Set<Observer> observers = new HashSet<>(); @Override public void registerObserver(Observer observer) { observers.add(observer); } @Override public void unregisterObserver(Observer observer) { observers.remove(observer); } @Override public void notifyObservers() { for (Observer observer : observers) observer.update(data); } public PerseveranceData getData() { return data; } // этот метод вызывается каждый раз при получении новых данных от ровера public void onNewData(PerseveranceData newData) { this.data = newData; notifyObservers(); } }<p>Perseverance хранит список Наблюдателей в переменной observers. Это множество (Set), так как в этом списке не допускаются дубликаты (представления одного типа), а также нам не важен порядок оповещения Наблюдателей.</p>
49
<p>При получении новых данных теперь вызывается метод notifyObservers, в котором, в свою очередь, вызывается метод update для каждого подписчика-Наблюдателя.</p>
49
<p>При получении новых данных теперь вызывается метод notifyObservers, в котором, в свою очередь, вызывается метод update для каждого подписчика-Наблюдателя.</p>
50
<p>Классы TemperatureDisplay, PressureDisplay и PhotoPublisher тоже изменятся:</p>
50
<p>Классы TemperatureDisplay, PressureDisplay и PhotoPublisher тоже изменятся:</p>
51
<ul><li>Укажем, что каждый из них теперь реализует интерфейс Observer.</li>
51
<ul><li>Укажем, что каждый из них теперь реализует интерфейс Observer.</li>
52
<li>Создадим конструктор с параметром типа Subject и будем регистрироваться в качестве Наблюдателя прямо при создании класса.</li>
52
<li>Создадим конструктор с параметром типа Subject и будем регистрироваться в качестве Наблюдателя прямо при создании класса.</li>
53
</ul><p>Например, TemperatureDisplay будет выглядеть так:</p>
53
</ul><p>Например, TemperatureDisplay будет выглядеть так:</p>
54
public class TemperatureDisplay implements Observer { public TemperatureDisplay(Subject subject) { subject.registerObserver(this); } @Override public void update(PerseveranceData data) { System.out.printf("Температура на Марсе - %2.0f градусов по Цельсию %n",data.getTemperature()); } }<p>Напишем тестовый пример - убедимся, что программа работает так, как мы ожидаем:</p>
54
public class TemperatureDisplay implements Observer { public TemperatureDisplay(Subject subject) { subject.registerObserver(this); } @Override public void update(PerseveranceData data) { System.out.printf("Температура на Марсе - %2.0f градусов по Цельсию %n",data.getTemperature()); } }<p>Напишем тестовый пример - убедимся, что программа работает так, как мы ожидаем:</p>
55
public class PerseveranceTest { public static void main(String[] args) { // создадим экземпляр ровера Perseverance perseverance = new Perseverance(); // и экземпляры классов-представлений TemperatureDisplay temperatureDisplay = new TemperatureDisplay(perseverance); PressureDisplay pressureDisplay = new PressureDisplay(perseverance); PhotoPublisher photoPublisher = new PhotoPublisher(perseverance); // отдельно регистрировать их в качестве Наблюдателей уже не нужно - они зарегистрировались в конструкторах // передадим роверу тестовые данные perseverance.onNewData(new PerseveranceData(-25, 0.6, "кратер Езеро")); System.out.println("--------------"); // теперь уберём из списка подписчиков temperatureDisplay perseverance.unregisterObserver(temperatureDisplay); // и снова вызовем обновление данных perseverance.onNewData(new PerseveranceData(-35, 0.5, "море Дождей")); } }<p>Так как мы создали экземпляры всех трёх классов-представлений, то при первом вызове метода onNewData все три класса должны получить обновления и обработать их в соответствии со своими реализациями метода update.</p>
55
public class PerseveranceTest { public static void main(String[] args) { // создадим экземпляр ровера Perseverance perseverance = new Perseverance(); // и экземпляры классов-представлений TemperatureDisplay temperatureDisplay = new TemperatureDisplay(perseverance); PressureDisplay pressureDisplay = new PressureDisplay(perseverance); PhotoPublisher photoPublisher = new PhotoPublisher(perseverance); // отдельно регистрировать их в качестве Наблюдателей уже не нужно - они зарегистрировались в конструкторах // передадим роверу тестовые данные perseverance.onNewData(new PerseveranceData(-25, 0.6, "кратер Езеро")); System.out.println("--------------"); // теперь уберём из списка подписчиков temperatureDisplay perseverance.unregisterObserver(temperatureDisplay); // и снова вызовем обновление данных perseverance.onNewData(new PerseveranceData(-35, 0.5, "море Дождей")); } }<p>Так как мы создали экземпляры всех трёх классов-представлений, то при первом вызове метода onNewData все три класса должны получить обновления и обработать их в соответствии со своими реализациями метода update.</p>
56
<p>Дальше мы убираем экран для вывода температуры из списка подписчиков, поэтому второй вызов обновления данных не должен привести к выводу очередного значения температуры.</p>
56
<p>Дальше мы убираем экран для вывода температуры из списка подписчиков, поэтому второй вызов обновления данных не должен привести к выводу очередного значения температуры.</p>
57
<p>Запустим приложение и убедимся в этом:</p>
57
<p>Запустим приложение и убедимся в этом:</p>
58
Вывод в консоли после запуска программы. Скриншот: Екатерина Степанова / Skillbox Media<p>Мы передавали новые данные от марсохода в методе update, но допустима и другая реализация: - передавать в метод update экземпляр Субъекта целиком и воспользоваться его методом getData для получения новых данных.</p>
58
Вывод в консоли после запуска программы. Скриншот: Екатерина Степанова / Skillbox Media<p>Мы передавали новые данные от марсохода в методе update, но допустима и другая реализация: - передавать в метод update экземпляр Субъекта целиком и воспользоваться его методом getData для получения новых данных.</p>
59
public class TemperatureDisplay implements Observer { public TemperatureDisplay(Subject subject) { subject.registerObserver(this); } public void update(Subject subject) { System.out.println(String.format("Температура на Марсе - %2.0f градусов по Цельсию", ((Perseverance) subject).getData().getTemperature())); } }<p>В этом случае мы приводим (преобразуем) экземпляр Subject к PerseveranceData, чтобы достучаться до температуры, давления и фотографий.</p>
59
public class TemperatureDisplay implements Observer { public TemperatureDisplay(Subject subject) { subject.registerObserver(this); } public void update(Subject subject) { System.out.println(String.format("Температура на Марсе - %2.0f градусов по Цельсию", ((Perseverance) subject).getData().getTemperature())); } }<p>В этом случае мы приводим (преобразуем) экземпляр Subject к PerseveranceData, чтобы достучаться до температуры, давления и фотографий.</p>
60
<p>Этот вариант позволяет использовать интерфейс Observer для других задач, не связанных с марсоходами, - так как его метод для обновления больше не завязан на формат данных ровера. О том, какие конкретно данные передаются, "знают" только реализации интерфейса.</p>
60
<p>Этот вариант позволяет использовать интерфейс Observer для других задач, не связанных с марсоходами, - так как его метод для обновления больше не завязан на формат данных ровера. О том, какие конкретно данные передаются, "знают" только реализации интерфейса.</p>
61
<p>Можно не изобретать велосипед и не писать свои интерфейсы Subject и Observer, а воспользоваться готовыми возможностями Java - в пакете java.beans есть класс PropertyChangeSupport и интерфейс PropertyChangeListener, которые отлично подходят для реализации паттерна "Наблюдатель".</p>
61
<p>Можно не изобретать велосипед и не писать свои интерфейсы Subject и Observer, а воспользоваться готовыми возможностями Java - в пакете java.beans есть класс PropertyChangeSupport и интерфейс PropertyChangeListener, которые отлично подходят для реализации паттерна "Наблюдатель".</p>
62
<p>Чтобы всё заработало, в класс Субъекта нужно добавить экземпляр PropertyChangeSupport, а классы Наблюдателей должны имплементить (реализовывать) интерфейс PropertyChangeListener.</p>
62
<p>Чтобы всё заработало, в класс Субъекта нужно добавить экземпляр PropertyChangeSupport, а классы Наблюдателей должны имплементить (реализовывать) интерфейс PropertyChangeListener.</p>
63
<p>Вот так будет выглядеть новая версия Perseverance:</p>
63
<p>Вот так будет выглядеть новая версия Perseverance:</p>
64
public class Perseverance { private PerseveranceData data; private final PropertyChangeSupport support = new PropertyChangeSupport(this); public void addPropertyChangeListener(PropertyChangeListener pcl) { support.addPropertyChangeListener(pcl); } public void removePropertyChangeListener(PropertyChangeListener pcl) { support.removePropertyChangeListener(pcl); } public PerseveranceData getData() { return data; } public void setData(PerseveranceData data) { this.data = data; } // этот метод вызывается каждый раз при получении новых данных от ровера public void onNewData(PerseveranceData newData) { support.firePropertyChange("perseverance", this.data, newData); this.data = newData; } }<p>А так - класс для вывода температуры:</p>
64
public class Perseverance { private PerseveranceData data; private final PropertyChangeSupport support = new PropertyChangeSupport(this); public void addPropertyChangeListener(PropertyChangeListener pcl) { support.addPropertyChangeListener(pcl); } public void removePropertyChangeListener(PropertyChangeListener pcl) { support.removePropertyChangeListener(pcl); } public PerseveranceData getData() { return data; } public void setData(PerseveranceData data) { this.data = data; } // этот метод вызывается каждый раз при получении новых данных от ровера public void onNewData(PerseveranceData newData) { support.firePropertyChange("perseverance", this.data, newData); this.data = newData; } }<p>А так - класс для вывода температуры:</p>
65
public class TemperatureDisplay implements PropertyChangeListener { @Override public void propertyChange(PropertyChangeEvent evt) { System.out.println(String.format("Температура на Марсе - %2.0f градусов по Цельсию", ((PerseveranceData) evt.getNewValue()).getTemperature())); } }<p>В классе<a>PropertyChangeSupport</a>есть методы для добавления и удаления новых Наблюдателей, а также метод для оповещения - firePropertyChange. Он принимает три параметра: тип изменений, предыдущие данные и новые данные.</p>
65
public class TemperatureDisplay implements PropertyChangeListener { @Override public void propertyChange(PropertyChangeEvent evt) { System.out.println(String.format("Температура на Марсе - %2.0f градусов по Цельсию", ((PerseveranceData) evt.getNewValue()).getTemperature())); } }<p>В классе<a>PropertyChangeSupport</a>есть методы для добавления и удаления новых Наблюдателей, а также метод для оповещения - firePropertyChange. Он принимает три параметра: тип изменений, предыдущие данные и новые данные.</p>
66
<p>Такой механизм даёт Наблюдателям дополнительные возможности. Как вариант, они могут реагировать только на отдельные категории событий или совершать какие-то действия только при достижении целевого уровня изменений: например, выводить температуру на экран, только если она изменилась не менее чем на 5 градусов.</p>
66
<p>Такой механизм даёт Наблюдателям дополнительные возможности. Как вариант, они могут реагировать только на отдельные категории событий или совершать какие-то действия только при достижении целевого уровня изменений: например, выводить температуру на экран, только если она изменилась не менее чем на 5 градусов.</p>
67
<p>В Java ещё есть интерфейс<strong>java.util.Observer</strong>и класс<strong>java.util.Observable</strong>. Для реализации паттерна "Наблюдатель" с их помощью можно наследовать класс Субъекта от Observable и имплементить Observer в своих Наблюдателях.</p>
67
<p>В Java ещё есть интерфейс<strong>java.util.Observer</strong>и класс<strong>java.util.Observable</strong>. Для реализации паттерна "Наблюдатель" с их помощью можно наследовать класс Субъекта от Observable и имплементить Observer в своих Наблюдателях.</p>
68
<p>Однако, начиная с Java 9, Observer и Observable<a>помечены</a><strong>deprecated</strong> - не рекомендуются к использованию. Вместо них лучше применять PropertyChangeSupport и PropertyChangeListener.</p>
68
<p>Однако, начиная с Java 9, Observer и Observable<a>помечены</a><strong>deprecated</strong> - не рекомендуются к использованию. Вместо них лучше применять PropertyChangeSupport и PropertyChangeListener.</p>
69
<p>Шаблон проектирования "Наблюдатель" полезен, когда одни объекты нужно оповещать об изменении других по подписке. С его помощью можно создать гибкое и легко расширяемое решение, при котором:</p>
69
<p>Шаблон проектирования "Наблюдатель" полезен, когда одни объекты нужно оповещать об изменении других по подписке. С его помощью можно создать гибкое и легко расширяемое решение, при котором:</p>
70
<ul><li>новые подписчики-Наблюдатели добавляются без изменений в существующих классах;</li>
70
<ul><li>новые подписчики-Наблюдатели добавляются без изменений в существующих классах;</li>
71
<li>реализации Субъекта и Наблюдателей отделены друг от друга, так что бизнес-логику в Наблюдателях можно менять как угодно без правок Субъекта.</li>
71
<li>реализации Субъекта и Наблюдателей отделены друг от друга, так что бизнес-логику в Наблюдателях можно менять как угодно без правок Субъекта.</li>
72
</ul><p>О других шаблонах проектирования, а также об алгоритмах, структурах данных, концепциях объектно-ориентированного программирования с примерами на языке Java - на курсе "<a>Профессия Java-разработчик PRO</a>". Освойте востребованный язык программирования, научитесь создавать качественные приложения под разные платформы, а Skillbox поможет с трудоустройством.</p>
72
</ul><p>О других шаблонах проектирования, а также об алгоритмах, структурах данных, концепциях объектно-ориентированного программирования с примерами на языке Java - на курсе "<a>Профессия Java-разработчик PRO</a>". Освойте востребованный язык программирования, научитесь создавать качественные приложения под разные платформы, а Skillbox поможет с трудоустройством.</p>
73
<a><b>Бесплатный курс по Python ➞</b>Мини-курс для новичков и для опытных кодеров. 4 крутых проекта в портфолио, живое общение со спикером. Кликните и узнайте, чему можно научиться на курсе. Смотреть программу</a>
73
<a><b>Бесплатный курс по Python ➞</b>Мини-курс для новичков и для опытных кодеров. 4 крутых проекта в портфолио, живое общение со спикером. Кликните и узнайте, чему можно научиться на курсе. Смотреть программу</a>