Паттерн Наблюдатель – поведенческий шаблон проектирования, определяет отношение «один ко многим» между субъектами таким образом, что при изменении состояния одного объекта происходит автоматическое оповещение и обновление всех зависимых компонентов.
Реализация: совет директоров нашего автогиганта в восторге от программы, которую мы написали при рассмотрении паттерна Стратегия и в качестве награды переводят в нас отдел передовых разработок, где мы будем разрабатывать программу для приборной панели нового поколения! На приборной панели будут отображаться три показателя: скорость, обороты двигателя и давление масла. Вся информация будет браться от центрального компьютера автомобиля.
Старайтесь добиваться слабой связи между взаимодействующими объектами
Давайте применим паттерн Наблюдатель к нашей задаче. У нас есть центральный компьютер автомобиля, который хранит в себе все характеристики автомобиля и постоянно их обновляет (это будет наш объект), приборная панель выступит в качестве наблюдателя изменения нужных нам характеристик (это будет наблюдатель). Центральный компьютер будет оповещать приборную панель об изменении характеристик, в которых она заинтересована, а та в свою очередь будет отображать изменения. Пока ничего сложного, создадим класс для нашего компьютера:
1 2 3 4 5 6 7 8 9 10 11 12 |
class CentralComp{ private int speed; // скорость private int rpm; // обороты двигателя private int oilPressure; // давление масла public void changeData(int speed, int rpm, int oilPressure){ // метод для изменения характеристик при движении автомобиля this.speed = speed; this.rpm = rpm; this.oilPressure = oilPressure; } } |
Класс центрального компьютера чрезвычайно прост, в нем присутствуют один метод для моделирования изменения характеристик при движении автомобиля (changeData).
Теперь создадим интерфейс для работы с наблюдателями. В интерфейсе будет три метода: добавление наблюдателя, удаление наблюдателя и оповещение наблюдателей. И сделаем так, чтобы класс центрального компьютера его реализовывал:
1 2 3 4 5 |
interface Notifier{ public void addObserver(Observer obs); public void removeObserver(Observer obs); public void notifyObserver(); } |
И так, интерфейс Notifier содержит в себе три метода: добавление наблюдателя (addObserver), удаление наблюдателя (removeObserver) и оповещение наблюдателей (notifyObserver), первые два метода принимают в качестве параметра переменную интерфейса наблюдателя, который мы создадим позже.
Приступим к реализации интерфейса Notifier классом центрального компьютера:
1 2 3 4 5 6 7 8 9 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 |
import java.util.ArrayList; import java.util.List; class CentralComp implements Notifier{ private List observers; // список наблюдателей private int speed; // скорость private int rpm; // обороты двигателя private int oilPressure; // давление масла public CentralComp(){ observers = new ArrayList(); } // добавить слушателя public void addObserver(Observer obs) { observers.add(obs); } // удалить слушателя public void removeObserver(Observer obs) { int i = observers.indexOf(obs); if (i >= 0){ observers.remove(i); } } // уведомить слушателей public void notifyObserver() { for (int i = 0; i < observers.size(); i++){ Observer obs = (Observer)observers.get(i); obs.update(speed, rpm, oilPressure); } } public void changeData(int speed, int rpm, int oilPressure){ // метод для изменения характеристик при движении автомобиля this.speed = speed; this.rpm = rpm; this.oilPressure = oilPressure; notifyObserver(); // уведомляем слушателей об изменениях } } |
Для хранения наблюдателей будем использовать переменную observers c типом List, которую инициализируем в конструкторе. Методы добавления и удаления наблюдателей очень просты, только следует отметить, что при удалении используется проверка на наличие удаляемого наблюдателя в списке. Метод notifyObserver с помощью цикла и метода update() интерфейса Observer оповещает всех слушателей об изменении данных. Вызов метода notifyObserver необходимо поместить в конце метода changeData.
Теперь приступим к работе над слушателями. В первую очередь создадим интерфейс, который будут реализовывать все слушатели:
1 2 3 |
interface Observer{ public void update(int speed, int rpm, int oilPressure); } |
В интерфейсе присутствует всего один метод, который необходимо реализовать для получения информации от нашего центрального компьютера (или объекта).
Далее создадим класс слушателя (приборной панели):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
class Dashboard implements Observer{ private Notifier notifier; private int speed; // скорость private int rpm; // обороты двигателя private int oilPressure; // давление масла public Dashboard(Notifier notifier){ this.notifier = notifier; notifier.addObserver(this); // регистрируем приборную панель в качестве наблюдателя } public void update(int speed, int rpm, int oilPressure) { this.speed = speed; this.rpm = rpm; this.oilPressure = oilPressure; show(); } // отображение на приборной панели информации public void show(){ System.out.println("Speed = " + speed + ", RPM = " + rpm + ", Oil pressure = " + oilPressure); } } |
Переменная notifier используется для регистрации класса приборной панели в качестве слушателя, которая происходит в конструкторе. Метод update() сохраняет полученную информацию о скорости, оборотах двигателя и давлении масла от объекта (центрального процессора) и отображает ее на приборной панели по средствам метода show().
Настало время проверить в деле нашу разработку.
1 2 3 4 5 6 7 8 |
public static void main(String[] args) { CentralComp cp = new CentralComp(); //создаем центральный процессор Dashboard db = new Dashboard(cp); //создаем приборную панель cp.changeData(10, 2000, 30); cp.changeData(20, 2500, 40); cp.changeData(60, 5000, 80); } |
Результат:
Speed = 10, RPM = 2000, Oil pressure = 30
Speed = 20, RPM = 2500, Oil pressure = 40
Speed = 60, RPM = 5000, Oil pressure = 80
В качестве закрепления материала, можете попробовать расщепить класс приборной панели на составляющие — индикатор скорости, индикатор давления масла и т.д., а приборную панель сделать интерфейсом с методом отображения информации, который индикаторы будут реализовывать.
В Java присутствует встроенная реализация паттерна Наблюдатель, на мой взгляд не самая удачная, поэтому сильное внимание на нее заострять я не буду. Попробуем переписать нашу программу под реализованный в Java паттерн, для этого нам потребуется класс java.util.Observer и интерфейс java.util.Observer.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
import java.util.Observable; import java.util.Observer; class CentralComp extends Observable{ private int speed; // скорость private int rpm; // обороты двигателя private int oilPressure; // давление масла public void changeData(int speed, int rpm, int oilPressure){ // метод для изменения характеристик при движении автомобиля this.speed = speed; this.rpm = rpm; this.oilPressure = oilPressure; setChanged(); // метод сообщает о необходимости обновить наблюдателей notifyObservers(); // уведомляем слушателей об изменениях } public int getSpeed(){ return speed; } public int getRPM(){ return rpm; } public int getOilPressure(){ return oilPressure; } } |
Как видите класс объекта у нас претерпел серьезные изменения, исчез конструктор с переменной для хранения слушателей, появились методы для получения скорости, оборотов и т.д.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
class Dashboard implements Observer{ private Observable observable; private int speed; // скорость private int rpm; // обороты двигателя private int oilPressure; // давление масла public Dashboard(Observable observable){ this.observable = observable; observable.addObserver(this); // регистрируем приборную панель в качестве наблюдателя } public void update(Observable obs, Object arg) { if (obs instanceof CentralComp){ CentralComp cp = (CentralComp)obs; this.speed = cp.getSpeed(); this.rpm = cp.getRPM(); this.oilPressure = cp.getOilPressure(); show(); } } // отображение на приборной панели информации public void show(){ System.out.println("Speed = " + speed + ", RPM = " + rpm + ", Oil pressure = " + oilPressure); } } |
Класс наблюдателя тоже постигли изменения, в частности конструктора и метода update().
Паттерн Наблюдатель полный код примера:
1 2 3 4 5 6 7 8 9 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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
package example; import java.util.ArrayList; import java.util.List; interface Notifier{ public void addObserver(Observer obs); public void removeObserver(Observer obs); public void notifyObserver(); } class CentralComp implements Notifier{ private List observers; // список наблюдателей private int speed; // скорость private int rpm; // обороты двигателя private int oilPressure; // давление масла public CentralComp(){ observers = new ArrayList(); } // добавить слушателя public void addObserver(Observer obs) { observers.add(obs); } // удалить слушателя public void removeObserver(Observer obs) { int i = observers.indexOf(obs); if (i >= 0){ observers.remove(i); } } // уведомить слушателей public void notifyObserver() { for (int i = 0; i < observers.size(); i++){ Observer obs = (Observer)observers.get(i); obs.update(speed, rpm, oilPressure); } } public void changeData(int speed, int rpm, int oilPressure){ // метод для изменения характеристик при движении автомобиля this.speed = speed; this.rpm = rpm; this.oilPressure = oilPressure; notifyObserver(); // уведомляем слушателей об изменениях } } interface Observer{ public void update(int speed, int rpm, int oilPressure); } class Dashboard implements Observer{ private Notifier notifier; private int speed; // скорость private int rpm; // обороты двигателя private int oilPressure; // давление масла public Dashboard(Notifier notifier){ this.notifier = notifier; notifier.addObserver(this); // регистрируем приборную панель в качестве наблюдателя } public void update(int speed, int rpm, int oilPressure) { this.speed = speed; this.rpm = rpm; this.oilPressure = oilPressure; show(); } // отображение на приборной панели информации public void show(){ System.out.println("Speed = " + speed + ", RPM = " + rpm + ", Oil pressure = " + oilPressure); } } public class Example { public static void main(String[] args) { CentralComp cp = new CentralComp(); //создаем центральный процессор Dashboard db = new Dashboard(cp); //создаем приборную панель cp.changeData(10, 2000, 30); cp.changeData(20, 2500, 40); cp.changeData(60, 5000, 80); } } |