Используйте тему, чтобы уведомить наблюдателей, когда происходит событие

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

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

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

Основываясь на информации о процессе на рисунке выше, давайте сначала определим класс Product:

class Product {
  constructor(pid, name, price) {
    this.pid = pid;
    this.name = name;
    this.price = price;
  }
  userIds = [];

  addNotification(userId) {
    this.userIds.push(userId);
  }

  notifyAllUser(price) {
    this.userIds.forEach((userId) => {
      console.log(
        `Dear user ${userId},the product you are concerned about:
          ${this.name},Price drop of $${this.price - price}.`
      ); 
    });
  }
}

const laptop = new Product("A001", "Notebook", 999);
laptop.addNotification("U001");
laptop.addNotification("U002");
laptop.notifyAllUser(899);

Когда приведенный выше код запустится успешно, консоль выведет следующие результаты:

Dear user U001,the product you are concerned about:
            Notebook,Price drop of $100.
Dear user U002,the product you are concerned about:
            Notebook,Price drop of $100.

В приведенном выше примере мы фактически использовали идею шаблона наблюдателя в определении класса Product. Шаблон наблюдателя, определяющий отношение "один ко многим", позволяет нескольким объектам-наблюдателям одновременно прослушивать определенный объект.

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

Шаблон наблюдателя в основном состоит из следующих четырех ролей:

  • Subject: абстрактный класс или интерфейс, который содержит методы для добавления наблюдателей, удаления наблюдателей и уведомления наблюдателей;
  • Concrete Subject : Конкретные предметные классы, которые реализуют методы, определенные в абстрактных предметных классах или интерфейсах.
  • Observer: абстрактный класс или интерфейс, который обычно содержит абстрактный метод, который получает обновления и вызывается при изменении определенного объекта;
  • Concrete Observer: реализовать абстрактный метод, определенный в абстрактном наблюдателе, для обработки определенной бизнес-логики после получения обновления.

Я буду использовать шаблон наблюдателя для реализации вышеупомянутой функции напоминания о снижении цены. Во-первых, давайте рефакторим класс Product и извлечем всю логику из исходного класса Product:

class Product {
  constructor(pid, name, price) {
    this.pid = pid;
    this.name = name;
    this.price = price;
  }
}

Затем мы определяем класс User (Concrete Observer), который содержит метод notify для получения уведомлений о снижении цен.

class User {
  constructor(uid, name) {
    this.uid = uid;
    this.name = name;
  }

  notify(product, price) {
    console.log(
      `Dear user ${this.uid},the product you are concerned about:
        ${product.name},Price drop of $${product.price - price}.`
    ); 
  }
}

Получив класс User, определите класс ProductSubject(Concrete Subject). Этот класс содержит три метода добавления наблюдателей, удаления наблюдателей и уведомления наблюдателей:

class ProductSubject  {
   observers = [];

  constructor(pid) {
    this.pid = pid;
  }

  addObserver(observer) {
    this.observers.push(observer);
  }

  deleteObserver(observer) {
    const n = this.observers.indexOf(observer);
    n != -1 && this.observers.splice(n, 1);
  }

  notifyObservers(product, price) {
    this.observers.forEach((observer) => observer.notify(product, price));
  }
}

Далее проверим работу напоминания о снижении цены товара после рефакторинга:

const laptop = new Product("A001", "Notebook", 999);
const productSubject = new ProductSubject(laptop.pid);
const u001 = new User("U001", "Bytefer");
const u002 = new User("U002", "Semlinker");
productSubject.addObserver(u001);
productSubject.addObserver(u002);

productSubject.notifyObservers(laptop, 899);

Когда приведенный выше код запустится успешно, консоль выведет следующие результаты:

Dear user U001,the product you are concerned about:
        Notebook,Price drop of $100.
Dear user U002,the product you are concerned about:
        Notebook,Price drop of $100.

В домене внешнего интерфейса шаблон Observer является очень распространенным шаблоном проектирования. Например, шаблон Observer можно увидеть в MutationObserver, IntersectionObserver, PerformanceObserver, ResizeObserver и ReportingObserver, все из которых являются веб-API. Кроме того, шаблон наблюдателя также используется для прослушивания событий и реагирования на данные (например, автоматические обновления страниц при изменении данных).

Чтобы добиться автоматического обновления страниц, нам необходимо выполнить два условия: первое — иметь возможность получать точные обновления, а второе — обнаруживать изменения данных. Чтобы добиться точных обновлений, необходимо для сбора функций обновления (наблюдателей), которые заинтересованы в изменении данных. После завершения сбора при обнаружении изменения данных соответствующая функция обновления может быть уведомлена.

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

Если вы понимаете принцип обработки данных Vue2, вы знакомы с кодом на рисунке выше, а второй шаг также называется сбором зависимостей. Используя Object.defineProperty API, мы можем перехватывать операции чтения и изменения данных.

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

Хорошо, это все, что касается шаблона наблюдателя. Для разработчиков, которые использовали Vue, если вам интересно, вы можете просмотреть код, связанный с реагированием на данные Vue2.

Позже я продолжу знакомить вас с другими шаблонами, если вам интересно, вы можете подписаться на меня в Medium или Twitter.

Если вы хотите изучить TypeScript, не пропустите серию статей Mastering TypeScript.



Дополнительные материалы на PlainEnglish.io. Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube и Discord .

Заинтересованы в масштабировании запуска вашего программного обеспечения? Ознакомьтесь с разделом Схема.