Изменение шаблона наблюдателя Java или шаблона издателя-подписчика - PullRequest
2 голосов
/ 28 июня 2019

Каков фактический паттерн наблюдателя снизу 2 варианта.

  1. В первом из них ответственность за подписку принимает обозреватель.
  2. Но во втором сценарии Publisher берет на себя ответственность за добавление определенного наблюдателя в список подписчиков.

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

    // Observer is taking the responsibility of subscribing 
    // Observer.java
    @Override
    public void subscribe(MessagePublisher publisher) {
        publisher.getObservers().add(this);
    }

2.

    // Publisher is taking the observer to subscribe
    // Publisher.java
     @Override
     public void addObserver(Observer observer) {
       observers.add(observer);
     }

Ответы [ 4 ]

1 голос
/ 28 июня 2019

Второй пример реализует то, что известно как Шаблон наблюдателя . Вы (наблюдатель) хотите получить уведомление о каком-либо событии, которое произошло в объекте X (наблюдаемом), верно? Чем вы скажете наблюдаемое, чтобы уведомить вас. Или, другими словами, вы начинаете слушать его, поэтому его также называют слушателем. The Observable не знает всех своих подписчиков. Он просто берет их и добавляет их в контейнер или коллекцию. Наблюдатель знает интерфейс IObserver, который реализуют все наблюдатели, и поэтому знает, какой метод (обратный вызов) вызывать.

Первый пример не имеет смысла. Наблюдаемое вызывает метод для наблюдателя, чтобы заставить его подписаться? Этот дизайн должен быть странным.

Допустим, у вас есть класс FileWriter, который выставляет событие FileWriter.Completed. У вас есть другой класс FileHandler, который управляет всеми файлами. FileHandler звонит FileWriter.write(filePath, data). После каждой операции FileHandler хочет показать сообщение пользователю. Таким образом, FileWriter is used by the FileHandler , but without knowing who's calling the write method. The FileHandler , one of the many consumers of FileWriter.write (string, string) , knows that FileWriter реализует наблюдаемый интерфейс, называемый IWriteToFileCompletedEvent. Поэтому он может быть уверен, что наблюдаемая имеет метод FileWriter.subscribeToWriteToFileCompleted(IObserver eventListener). Как вы хотите, чтобы FileWriter знал обо всех потребителях его событий до того, как они подпишутся ?. Думаю об этом. Это было бы странным API, например: FileWriter.write(filepath, data, this), чтобы дать FileWriter ссылку на слушателя. Затем FileWriter принимает ссылку на слушателя и вызывает listener.subscribe(this). Теперь FileHandler (слушатель) имеет бесполезную ссылку на FileWriter (бесполезно, потому что он знает FileWriter раньше, так как он вызвал FileWriter.write на нем), поэтому слушатель принимает ссылку FileWriter, которую он уже должен позвонить observable.getObservers().add(this). Я получил удар при наборе этого. Серьезно, это какие-то безумные спагетти.

Так будет выглядеть реальная реализация в «реальной жизни»

// The interface for the publisher or observable of a special event
interface IWriteToFileCompletedEvent {
  void subscribeToWriteToFileCompleted(IWriteToFileCompletedListener observer);
  void unsubscribeToWriteToFileCompleted(IWriteToFileCompletedListener observer);
}

// The interface for the observer of a special event
interface IWriteToFileCompletedListener {
  void notify(string filePathOfCompletedFile);
}

// The observable that exposes a special event
class FileWriter implements IWriteToFileCompletedEvent {
  private List<IWriteToFileCompletedListener> writeToFileCompletedListeners = new ArrayList<>();

  public void subscribeToWriteToFileCompleted(IWriteToFileCompletedListener observer) {
    this.writeToFileCompletedListeners.add(observer);
  }

  public void unsubscribeToWriteToFileCompleted(IWriteToFileCompletedListener observer) {
    this.writeToFileCompletedListeners.remove(observer);
  }

  public void writeToFile(string filePath, string data) {
    // Write data to file
    // Once done notify all listeners of the write completed event
    for (IWriteToFileCompletedListener observer : this.writeToFileCompletedListeners) {
      observer.notify(filePath);
    }
  } 
}

// The observer of a special event
class ContactsHandler implements IWriteToFileCompletedListener {
  private FileWriter fileWriter = new FileWriter();

  public void saveUserContactToFile(string filePath, string userInput) {
    this.fileWriter.subscribeToWriteToFileCompleted(this);
    this.fileWriter.writeToFile(filePath, userInput);
  }

  // Implementation of interface IWriteToFileCompletedListener 
  public void notify(string filePath) {
    this.fileWriter.unsubscribeToWriteToFileCompleted(this);
    this.messageDialog.show("The new contact was successfully saved to " + filePath);
  }
}

// Another observer of a special event
class SettingsHandler implements IWriteToFileCompletedListener {
  private FileWriter fileWriter = new FileWriter();

  public void saveUserSettingsToFile(string filePath, string userSettings) {
    this.fileWriter.subscribeToWriteToFileCompleted(this);
    this.fileWriter.writeToFile(filePath, userSettings);
  }

  // Implementation of interface IWriteToFileCompletedListener 
  public void notify(string filePath) {
    this.fileWriter.unsubscribeToWriteToFileCompleted(this);
    this.messageDialog.show("The new settings were successfully saved to " + filePath);
  }
}

Классы будут взаимодействовать так

public void main(strng[] args {
  SettingsHandler settingsHandler = new SettingsHandler();
  ContactsHandler contactsHandler = new ContactsHandler();

  // Imaging this method receives user input fromo the UI:
  string newContact = textBox.gettext();
  this.contactsHandler.saveUserContactToFile("C:\Contacts.txt", newContact);

  // While waiting for the message to show the user adjusted some settings and clicked 'save'
  string changedSettings = getChangedSettings();
  this.settingsHandler.saveUserSettingsToFile("C:\UserSettings.txt", changedSettings);

  // After a while the user sees the messages send from the event listeners.
}

Несколько объектов совершенно другого типа (SettingsHandler и ContactsHandler) подписались на одно и то же событие. Все чисто. Объект, который использует объект, который он хочет наблюдать, подписывается. Класс, который можно наблюдать, наблюдаемый, просто уведомляет всех подписчиков. вот и все.

Подводя итог: ваше решение 1 совсем не удобно (хотя вы можете заставить его работать) и выдает ужасный код.

Решение 2 фактически следует шаблону, который описывает, как реализовать события. Эта реализация доказана и хорошо зарекомендовала себя, поскольку она достигает цели введения событий и создает чистый и читаемый код.

1 голос
/ 28 июня 2019

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

В структуре шаблона, описанной в книге GoF и в других местах, Subject имеет метод AttachDetach) как часть его интерфейса. Подразумевается, что любой объект со ссылкой на субъект может позвонить Attach и выполнить эту регистрацию.

1 голос
/ 28 июня 2019

Я подозреваю, что они оба. Им обоим нужно будет реализовать интерфейс Observer.

0 голосов
/ 30 июня 2019

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

...