[Серия «Учимся вместе»] Режим наблюдателя: я не слежу за вами

Шаблоны проектирования
[Серия «Учимся вместе»] Режим наблюдателя: я не слежу за вами

намерение

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

Псевдоним: шаблон публикации-подписки

Рождение шаблона наблюдателя

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

Речь это:

[Продукт]: Разработчик, мне нужно, чтобы вы спроектировали большой экран для отображения прогноза погоды, метеостанция будет отправлять вам данные, вам нужно отобразить их на большом экране, хорошо?

【Разработка】: OJBK! Сделай все за секунды! Код сейчас!

void getTemperature () {
    // 从气象站获取发送过来的温度数据
    // getData();
    
    
    // ................................
    // 显示到大屏里面去
    // showDataToScreen();
    // ................................
}

void getMisture () {
    // 从气象站获取发送过来的湿度数据
    // getData();
    
    
    // ................................
    // 显示到大屏里面去
    // showDataToScreen();
    // ................................
}

void getAirindex () {
    // 从气象站获取发送过来的空气指数数据
    // getData();
    
    
    // ................................
    // 显示到大屏里面去
    // showDataToScreen();
    // ................................
}

【БОСС】: Низкий поклон! Планируете ли вы выполнять код CV каждый раз, когда получаете данные? Ты не устал?

[Разработчик]: Босс, я совсем не устал! Просто скопируйте и вставьте его!

[БОСС]: Что, если мне не нужно сейчас синхронно обновлять индекс погоды? удалить код?

【Развитие】: Да! Его можно удалить за считанные секунды! ( •̀ ω •́ )✧

【БОСС】: Переписать😃

Основной код HeadFirst

Итак, мы начали путешествие с чтения классических книг о шаблонах проектирования.

/**
 * 观察主题接口
 */
public interface Observable{
    public void addObserver(Observer observer);     // 添加观察者
    public void removeObserver(Observer observer);  // 移除观察者
    public void notifyObservers(WeatherData data);  // 通知所有观察者
}


/**
 * 观察者
 */
public interface Observer {
    public abstract void update(WeatherData data);
}


/**
 * 天气主题
 *
 */
public class Weather implements Observable {
    private List<Observer> observers = new ArrayList<>();
    
    public void addObserver(Observer observer) {
        observers.add(observer);
    }
    
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }
    
    public void notifyObservers(WeatherData data) {
        for (Observer observer : observers)
            observer.update(data);
    }
}

Идея оформления паттерна Observer:

  • Цель Subject (контейнер) предоставляет интерфейсы для регистрации и удаления наблюдателей и обновления интерфейсов.
  • Observer определяет интерфейс обновления для объектов, которые необходимо уведомлять об изменении цели.
  • Уведомлять каждого наблюдателя, когда состояние ConcreteSubject изменяется
  • ConcreteObserver (конкретный наблюдатель) реализует интерфейс обновления Observer.

Проще говоря,

  1. Нам нужен интерфейс для определения регистрации, удаления и обновления интерфейсов.
  2. Затем интерфейс реализуется конкретной целью (классом), и в классе создается контейнер для хранения объектов, которые необходимо уведомить.
  3. Объект, который необходимо уведомить, должен реализовать метод обновления обновления в интерфейсе Observer.
  4. Зарегистрируйте объект наблюдателя в контейнере и вызовите метод обновления всех объектов класса контейнера при обновлении конкретной цели.

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

Шаблон наблюдателя в JDK

В JDK уже есть конкретная реализация шаблона наблюдателя, и код очень простой, как показано ниже:

конкретная цель:

public class ObservableApp extends Observable {

    private long curr;
    
    public ObservableApp(long curr) {
        this.curr = curr;
    }
    
    public void change(long newStr) {
        this.curr = newStr;
        
        // 更改状态,发送通知
        setChanged();
        notifyObservers(newStr);
    }

    @Override
    protected synchronized void setChanged() {
        super.setChanged();
    }
}

Конкретные наблюдатели:

public class ObserverA implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        System.out.println(MessageFormat.format("ObserverA -> {0} changed, Begin to Work. agr is:{1}", o.getClass().getSimpleName(), arg));
    }
}

Основной метод:

public class App {
    
    public static void main(String[] args) throws InterruptedException {
        ObservableApp app = new ObservableApp(System.currentTimeMillis());
        System.out.println(app.getCurr());
        app.addObserver(new ObserverA());
        app.addObserver(new ObserverB());

        Thread.sleep(1000);
        
        long curr = System.currentTimeMillis();
        app.change(curr);
        System.out.println(app.getCurr());
    }
}

// 输出如下:
// 1589688733464
// ObserverB -> ObservableApp changed, Begin to Work. agr is:1,589,688,734,469
// ObserverA -> ObservableApp changed, Begin to Work. agr is:1,589,688,734,469
// 1589688734469

толкающий режим

Уведомление отправляется наблюдателю, и уведомление содержит параметры, которые являются push-уведомлениями, соответствующими методу JDK: notifyObservers(Object arg)

режим вытягивания

Уведомление отправляется наблюдателю. Уведомление не содержит параметров. Наблюдатель должен активно вызывать метод get для получения данных.

Соответствует: notifyObservers () в методе JDK, он только информирует наблюдателя об изменении данных Что касается деталей данных, наблюдатель должен взять на себя инициативу, чтобы получить данные в теме

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

Принципы дизайна, которым нужно следовать

  1. "пакетные изменения"
    • Что часто меняется в паттерне наблюдателя, так это состояние субъекта, а также количество и тип наблюдателей.
    • Мы можем изменять объекты, зависящие от состояния темы, но не обязательно менять саму тему, это планируется заранее
  2. "Программирование для интерфейса"
    • И субъекты, и наблюдатели используют интерфейсы
    • Наблюдатели регистрируются у субъекта, используя интерфейс субъекта.
    • Субъект использует интерфейс наблюдателя для уведомления наблюдателя, что может заставить их нормально взаимодействовать, и в то же время имеет характеристики слабой связи.
  3. "комбинации многократного использования"
    • Паттерн Observer использует композицию для объединения многих наблюдателей в тему.
    • Отношения между ними не получаются через наследование, а динамически изменяются во время выполнения.

Какая сцена подходит для использования

Шаблон наблюдателя используется, когда между объектами существует отношение «один ко многим», например, когда объект изменяется, его зависимые объекты автоматически уведомляются. Паттерн наблюдателя — это поведенческий паттерн

Код / практическое применение в жизни

  • Например, внимание подписки в общедоступной учетной записи WeChat, после подписки статьи, опубликованные общедоступной учетной записью, будут распространяться на каждую учетную запись в режиме реального времени.
  • Другой пример, когда мы используем Keep для бега, если вы бежите достаточно страстно, он подскажет вам, поздравляю, вы побили лучший рекорд в пять километров! Такие голосовые напоминания должны запускаться, а не обнаруживаться в реальном времени, верно? (Обнаружение в реальном времени бессмысленно и снижает производительность). Здесь вы можете использовать шаблон наблюдателя для проектирования и развязки.

Наконец

"Прикрепите диаграмму UML для шаблона наблюдателя в книге GOF:"

观察者模式UML图
UML-диаграмма шаблона наблюдателя

Связанные ссылки на код

Адрес GitHub

  • С учетом случаев в двух классических данных "HeadFirst" и "GOF"
  • Предоставляет дружественное руководство по чтению