24

The java.util.Observer and java.util.Observable are ugly. They require the sorts of casts that make type-safety fans uncomfortable, and you can't define a class to be an Observer of multiple things without ugly casts. In fact, in "How do I know the generic object that the Observer class sends in Java?", an answerer says that only one type of data should be used in each observer / observable.

I'm trying to make a generic version of the observer pattern in Java to get round both these problems. It's not unlike the one in the previously mentioned post, but that question was not obviously resolved (the last comment is an unanswered question from the OP).

Community
  • 1
  • 1
hcarver
  • 7,126
  • 4
  • 41
  • 67

6 Answers6

16

Observer.java

package util;

public interface Observer<ObservedType> {
    public void update(Observable<ObservedType> object, ObservedType data);
}

Observable.java

package util;

import java.util.LinkedList;
import java.util.List;

public class Observable<ObservedType> {

    private List<Observer<ObservedType>> _observers = 
      new LinkedList<Observer<ObservedType>>();

    public void addObserver(Observer<ObservedType> obs) {
        if (obs == null) {
            throw new IllegalArgumentException("Tried
                      to add a null observer");
        }
        if (_observers.contains(obs)) {
            return;
        }
        _observers.add(obs);
    }

    public void notifyObservers(ObservedType data) {
        for (Observer<ObservedType> obs : _observers) {
            obs.update(this, data);
        }
    }
}

Hopefully this will be useful to someone.

hcarver
  • 7,126
  • 4
  • 41
  • 67
  • 1
    Why would you do `if(!contains) { return; } then add()` when you could simply do `if (contains) { add(); }`. Besides, you should add some text to your `IllegalArgumentException` – Alex Nov 13 '12 at 14:49
  • 1
    Guess you mean `if(contains)` first time and `if(!contains)` the second time round. Because I prefer the style, is all. It makes no difference. And, yeah, I'll edit the answer with fleshed-out text for the exception. – hcarver Nov 13 '12 at 14:52
  • 3
    perfect... boom!! I think it would be good we use Set instead List to handle uniqueness. – Mehraj Malik Nov 21 '17 at 12:04
12

I prefer using an annotation so a listener can listen to different types of events.

public class BrokerTestMain {
    public static void main(String... args) {
        Broker broker = new Broker();
        broker.add(new Component());

        broker.publish("Hello");
        broker.publish(new Date());
        broker.publish(3.1415);
    }
}

class Component {
    @Subscription
    public void onString(String s) {
        System.out.println("String - " + s);
    }

    @Subscription
    public void onDate(Date d) {
        System.out.println("Date - " + d);
    }

    @Subscription
    public void onDouble(Double d) {
        System.out.println("Double - " + d);
    }
}

prints

String - Hello
Date - Tue Nov 13 15:01:09 GMT 2012
Double - 3.1415

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Subscription {
}

public class Broker {
    private final Map<Class, List<SubscriberInfo>> map = new LinkedHashMap<Class, List<SubscriberInfo>>();

    public void add(Object o) {
        for (Method method : o.getClass().getMethods()) {
            Class<?>[] parameterTypes = method.getParameterTypes();
            if (method.getAnnotation(Subscription.class) == null || parameterTypes.length != 1) continue;
            Class subscribeTo = parameterTypes[0];
            List<SubscriberInfo> subscriberInfos = map.get(subscribeTo);
            if (subscriberInfos == null)
                map.put(subscribeTo, subscriberInfos = new ArrayList<SubscriberInfo>());
            subscriberInfos.add(new SubscriberInfo(method, o));
        }
    }

    public void remove(Object o) {
        for (List<SubscriberInfo> subscriberInfos : map.values()) {
            for (int i = subscriberInfos.size() - 1; i >= 0; i--)
                if (subscriberInfos.get(i).object == o)
                    subscriberInfos.remove(i);
        }
    }

    public int publish(Object o) {
        List<SubscriberInfo> subscriberInfos = map.get(o.getClass());
        if (subscriberInfos == null) return 0;
        int count = 0;
        for (SubscriberInfo subscriberInfo : subscriberInfos) {
            subscriberInfo.invoke(o);
            count++;
        }
        return count;
    }

    static class SubscriberInfo {
        final Method method;
        final Object object;

        SubscriberInfo(Method method, Object object) {
            this.method = method;
            this.object = object;
        }

        void invoke(Object o) {
            try {
                method.invoke(object, o);
            } catch (Exception e) {
                throw new AssertionError(e);
            }
        }
    }
}
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • This is definitely a neat solution, but to me the `Interface`-based one feels like a better description of the intention of the code. Interested to know what you think. – hcarver Nov 13 '12 at 21:50
  • I +1'ed you answer and I think its a good solution. Its the sort of pattern I use often. It is however limited to one broker per message type, and each listener can only receive one type of object. It is not as generic is having a broker which can handle any object type and listeners which can listen to multiple types. – Peter Lawrey Nov 14 '12 at 08:45
  • 1
    Hadn't thought of that, you're right. Thanks for the answer, very useful to me, hopefully to other people too. – hcarver Nov 14 '12 at 11:27
5

A modern update: ReactiveX is a very nice API for asynchronous programming based on the Observer pattern, and it's fully generic. If you're using Observer/Observable to "stream" data or events from one place in your code to another, you should definitely look into it.

It's based on functional programming, so it looks very sleek with Java 8's lambda syntax:

Observable.from(Arrays.asList(1, 2, 3, 4, 5))
        .reduce((x, y) -> x + y)
        .map((v) -> "DecoratedValue: " + v)
        .subscribe(System.out::println);
Lynn
  • 10,425
  • 43
  • 75
  • +1 for ReactiveX, also this can be used for Android developing ;) (for example via https://github.com/ReactiveX/RxAndroid) – optional May 05 '17 at 09:05
3

I once wrote a generic implementation of the observer pattern for Java using dynamic proxies. Here's a sample of how it could be used:

Gru gru = new Gru();
Minion fred = new Minion();
fred.addObserver(gru);
fred.moo();

public interface IMinionListener
{
    public void laughing(Minion minion);
}

public class Minion extends AbstractObservable<IMinionListener>
{
    public void moo()
    {
        getEventDispatcher().laughing(this);
    }
}

public class Gru implements IMinionListener
{
    public void punch(Minion minion) { ... }

    public void laughing(Minion minion)
    {
        punch(minion);
    }
}

The full source code of AbstractObservable is available on pastebin. Way back I blogged about how it works in a bit more detail, also referring to related projects.

Jaana wrote an interesting summary of different approaches, also contrasting the dynamic proxy approach with others. Much thanks of course goes to Allain Lalonde from which I got the original idea. I still haven't checked out PerfectJPattern, but it might just contain a stable implementation of the observer pattern; at least it seems like a mature library.

Steven Jeuris
  • 18,274
  • 9
  • 70
  • 161
3

Try to use class EventBus of Guava.

You can declare a Observer like this:

    public class EventObserver {
        @Subscribe 
        public void onMessage(Message message) {
            ...
        }
    }

New a EventBus like this:

EventBus eventBus = new EventBus();

And register Observer like this:

eventBus.register(new EventObserver());

Last notify Observer like:

eventBus.post(message);
Kedron
  • 343
  • 1
  • 3
  • 9
0

I found a similar request but it was rather on codereview. I think it's worth mentioning it here.

import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Supplier;

/**
 * like java.util.Observable, But uses generics to avoid need for a cast.
 *
 * For any un-documented variable, parameter or method, see java.util.Observable
 */
public class Observable<T> {

    public interface Observer<U> {
        public void update(Observable<? extends U> observer, U arg);
    }

    private boolean changed = false;
    private final Collection<Observer<? super T>> observers;

    public Observable() {
        this(ArrayList::new);
    }

    public Observable(Supplier<Collection<Observer<? super T>>> supplier) {
        observers = supplier.get();
    }

    public void addObserver(final Observer<? super T> observer) {
        synchronized (observers) {
            if (!observers.contains(observer)) {
                observers.add(observer);
            }
        }
    }

    public void removeObserver(final Observer<? super T> observer) {
        synchronized (observers) {
            observers.remove(observer);
        }
    }

    public void clearObservers() {
        synchronized (observers) {
            this.observers.clear();
        }
    }

    public void setChanged() {
        synchronized (observers) {
            this.changed = true;
        }
    }

    public void clearChanged() {
        synchronized (observers) {
            this.changed = false;
        }
    }

    public boolean hasChanged() {
        synchronized (observers) {
            return this.changed;
        }
    }

    public int countObservers() {
        synchronized (observers) {
            return observers.size();
        }
    }

    public void notifyObservers() {
        notifyObservers(null);
    }

    public void notifyObservers(final T value) {
        ArrayList<Observer<? super T>> toNotify = null;
        synchronized(observers) {
            if (!changed) {
                return;
            }
            toNotify = new ArrayList<>(observers);
            changed = false;
        }
        for (Observer<? super T> observer : toNotify) {
            observer.update(this, value);
        }
    }
}

Original answer from codereview stackexchange

neo7
  • 654
  • 5
  • 14