javaoopdesign-patternsobserver-patterntemplate-method-pattern

Observer with full transparency


I'm implementing observer pattern in the following way:

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?
    }
}

And this works of course, but whoever is implementing ObservableLayer needs to remember to call this.notifyObservers() in the adjustString method. This is not that of a big deal, but I wanted to see if there is a way to completely hide this.

So far, I only have this idea (using template method):

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
    }
}

but here I don't like that method name changed to doAdjustString, and it is not anymore uniform between other layer implementations (layers that directly implement Layer interface).

Is there any easy way to have this functionallity, but to keep public void adjustString(Set<String> strings) signature in MyLayer class?


Solution

  • One way would be to use a Decorator instance that holds an ObservableLayer instance and delegates to it.

        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();
          }
        }
    

    This assumes that calling code is working using references to Layer instead of ObservableLayer.

    If calling code has to work using references to ObservableLayer then maybe it is better to refactor ObservableLayer to be an interface having the methods to register listeners, remove them and notify them. This interface also extends the Layer interface.

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

    The abstract class ObservableLayer changes to implement IObservableLayer instead of Layer directly. This class remains public to support application classes to define variations of observable layers.

    Next an internal decorator for observable layers can be defined as shown below.

        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();
          }
        }
    

    Please note how the notification is done in this case.

    Now instances of IObservableLayer can be created as

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

    Factory methods will be helpful here as they can be defined to handle creation of various application-level observable layer classes so that the instances can be created consistently that return an IObservableLayer which is decorated. That will free up developers from knowing how to use the decorator and allow the decorator to be an internal utility.