Наблюдатель с полной прозрачностью

#java #oop #design-patterns #observer-pattern #template-method-pattern

#java #ооп #шаблоны проектирования #наблюдатель-шаблон #шаблон-метод-шаблон

Вопрос:

Я реализую шаблон наблюдателя следующим образом:

 interface Layer{
    void adjustString(Set<String> strings);
}

interface NotifiableLayer extends Layer{
    void layerAdjusted(Layer layer);
}

abstract class ObservableLayer implements Layer{
    Set<NotifiableLayer> observers = new HashSet<>();

    void addObserver(NotifiableLayer layer){
        observers.add(layer);
    }
    void removeObserver(NotifiableLayer layer){
        observers.remove(layer);
    }

    void notifyObservers(){
        observers.forEach(l -> l.layerAdjusted(this));
    }
}

class MyLayer extends ObservableLayer{
    @Override
    public void adjustString(Set<String> strings) {
        this.notifyObservers(); //can this be auto?
    }
}
  

И это, конечно, работает, но тот, кто реализует ObservableLayer , должен помнить о вызове this.notifyObservers() adjustString метода. Это не имеет большого значения, но я хотел посмотреть, есть ли способ полностью скрыть это.

Пока у меня есть только эта идея (с использованием метода шаблона):

 abstract class ObservableLayer implements Layer{
    //...methods removed for simplicity

    @Override
    public void adjustString(Set<String> strings) {
        this.doAdjustString(strings);
        this.notifyObservers(); //<---- here is auto
    }

    abstract void doAdjustString(Set<String> strings);
}

class MyLayer extends ObservableLayer{
    @Override
    public void doAdjustString(Set<String> strings) {
        //now notification is in base adjustString
    }
}
  

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

Есть ли какой-нибудь простой способ получить эту функциональность, но сохранить public void adjustString(Set<String> strings) подпись в MyLayer классе?

Ответ №1:

Одним из способов было бы использовать экземпляр декоратора, который содержит ObservableLayer экземпляр и делегирует ему.

     final class LayerDecorator implements Layer {
      final private ObservableLayer delegate;

      public LayerDecorator(ObservableLayer delegate) {
        this.delegate = delegate;
      }

      @Override
      public void adjustString(Set<String> strings) {
        delegate.adjustString(strings);
        delegate.notifyObservers();
      }
    }
  

Это предполагает, что вызывающий код работает с использованием ссылок на Layer вместо ObservableLayer .

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

    interface IObservableLayer extends Layer {
     void addObserver(NotifiableLayer layer);
     void removeObserver(NotifiableLayer layer);
     void notifyObservers();
   }
  

Абстрактный класс ObservableLayer изменяется на IObservableLayer implementate вместо Layer прямого. Этот класс остается открытым для поддержки классов приложений для определения вариантов наблюдаемых слоев.

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

     final class ObservableLayerDecorator implements IObservableLayer {
      final private ObservableLayer delegate;

      public ObservableLayerDecorator(ObservableLayer delegate) {
        this.delegate = delegate;
      }

      @Override
      public void addObserver(NotifiableLayer layer) {
        delegate.addObserver(layer);
      }

      @Override
      public void removeObserver(NotifiableLayer layer) {
        delegate.removeObserver(layer);
      }

      @Override
      public void notifyObservers() {
        delegate.notifyObservers();
      }

      @Override
      public void adjustString(Set<String> strings) {
        delegate.adjustString(strings);
        this.notifyObservers();
      }
    }
  

Пожалуйста, обратите внимание, как выполняется уведомление в этом случае.

Теперь экземпляры IObservableLayer могут быть созданы как

     IObservableLayer observableLayer = new ObservableLayerDecorator(new MyClass());
  

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

Комментарии:

1. Спасибо за еще один подход — стандартный «предпочтение композиции перед наследованием».

Ответ №2:

Другой подход — аспектно-ориентированное программирование.

Следующий пример использует AspectJ для перехвата любого public выполнения метода в расширяющемся классе Observable и вызова notifyObservers() для того же объекта.

 import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class EventAspect {
    @AfterReturning("execution(public * Observable.*(..)) amp;amp; target(observable)")
    public void notifyObservers(Observable observable) {
        observable.notifyObservers();
    }
}