Search code examples
javamodel-view-controllerobserver-patternloose-coupling

Loosely coupled observer pattern


I realise this subject has been covered to death but I am still struggling and could do with some specific help.

My aim is to implement a simple Observer pattern between some kind of observable (lets say a Dog) and some kind of listener (lets say Owner).

Eventually the Owner would be a 'view' and the Dog a 'model' in a MVC paradigm. I am using Dog and Owner just to try and simplify things here.

I have attempted to use Java's built in Observer / Observable classes but have realised how bad the Observers update() method is - it receives a POJO and I would need to check / cast that POJO in the update() method. I would much prefer to have my 'update()' method receive something it can expect.

So, I followed a few tutorials, including this one which uses the Dog/Owner as an example:

http://www.youtube.com/watch?v=qw0zZAte66A

Here I have been shown how to roll my own Observer/Observed classes. In pseudo code, what I now have is this:

Dog/Model {

    List listeners;

    public fireDogHungryEvent() {

        foreach listener {
            listener.onDogHungry(this);
        }
    }

    public fireDogPeeEvent() {

        foreach listener {
            listener.onDogNeedsToPee(this);
        }
    }

    public getHungerLevel() { return hungerLevel; }
    public getBladderCapacity() { return bladderCapacity; }
}

Owner/View {

    public onDogHungry(model) {
        println(model.getHungerLevel());
    }

    public onDogNeedsToPee(model) {
        println(model.getBladderCapacity());
    }
}

So now rather than one update() method, I have methods that handle specific events. Brilliant. I am currently happy with the Owner/view class. It knows about the Dog/model's methods and that is fine (I think).

What I do not like is that the Dog/model has references to methods in the Owner/view. I've read countless times and completely agree that a model should not be tightly coupled to its views such as it seems to be above. I am also not keen on all the 'fire' methods in the Dog/model doing nearly the same thing; looping over all it's listeners and just calling a different method on each listener.

Is it possible to decouple this relationship further and not have the Dog/model call specific methods? If so, what is the best way to go about receiving Dog/Model data into the Owner/view and working with it appropriately?

Thanks


Solution

  • You should interface away knowledge of specific implementation from both the Observer and the Observable

    public enum EventType {
    
        HUNGRY,
        PEE;
    }
    
    public interface DogEvent {
    
        EventType getType();
    }
    
    public interface DogListener {
    
        void fireEvent(DogEvent event);
    }
    
    public class Dog {
    
        private final Set<DogListener> listeners = new CopyOnWriteArraySet<DogListener>();
    
        public void register(final DogListener dogListener) {
            listeners.add(dogListener);
        }
    
        public void unregister(final DogListener dogListener) {
            listeners.remove(dogListener);
        }
    
        public void firePeeEvent() {
            fireEvent(new DogEvent() {
                @Override
                public EventType getType() {
                    return EventType.PEE;
                }
            });
        }
    
        public void fireHungryEvent() {
            fireEvent(new DogEvent() {
                @Override
                public EventType getType() {
                    return EventType.HUNGRY;
                }
            });
        }
    
        private void fireEvent(final DogEvent dogEvent) {
            for (final DogListener listener : listeners) {
                listener.fireEvent(dogEvent);
            }
        }
    }
    
    public class Owner implements DogListener {
    
        @Override
        public void fireEvent(DogEvent event) {
            switch (event.getType()) {
                case PEE:
                    System.out.println("Someone take the dog out");
                    break;
                case HUNGRY:
                    System.out.println("I can't believe the dog is hungry _again_!");
                    break;
            }
        }
    }
    

    In this case the Dog does not know about the implementation of the Owner it just known that the Owner is a DogListener.

    The Owner on the other hand does not know about the Dog it just knows that it has an incoming DogEvent.