/******************************************************************* * This class replaces the Multicaster class that's described in * <i>Taming Java Threads</i>. It's better in almost every way * (It's smaller, simpler, faster, etc.). The primary difference * between this class and the original is that I've based * it on a linked-list, and I've used a Strategy object to * define how to notify listeners, thereby makeing the interface * much more flexible. * <p> * The <code>Publisher</code> class provides an efficient thread-safe means of * notifying listeners of an event. The list of listeners can be * modified while notifications are in progress, but all listeners * that are registered at the time the event occurs are notified (and * only those listeners are notified). The ideas in this class are taken * from the Java's AWTEventMulticaster class, but I use an (iterative) * linked-list structure rather than a (recursive) tree-based structure * for my implementation. * <p> * Here's an example of how you might use a <code>Publisher</code>: * <PRE> * class EventGenerator * { interface Listener * { notify( String why ); * } * * private Publisher publisher = new Publisher(); * * public void addEventListener( Listener l ) * { publisher.subscribe(l); * } * * public void removeEventListener ( Listener l ) * { publisher.cancelSubscription(l); * } * * public void someEventHasHappend(final String reason) * { publisher.publish * ( * // Pass the publisher a Distributor that knows * // how to notify EventGenerator listeners. The * // Distributor's deliverTo method is called * // multiple times, and is passed each listener * // in turn. * * new Publisher.Distributor() * { public void deliverTo( Object subscriber ) * { ((Listener)subscriber).notify(reason); * } * } * ); * } * } * </PRE> * Since you're specifying what a notification looks like * by defining a Listener interface, and then also defining * the message passing symantics (inside the Distributor implementation), * you have complete control over what the notification interface looks like. * * @include /etc/license.txt */ public class Publisher { public interface Distributor { void deliverTo( Object subscriber ); // the Visitor pattern's } // "visit" method. // The Node class is immutable. Once it's created, it can't // be modified. Immutable classes have the property that, in // a multithreaded system, access to the does not have to be // synchronized, because they're read only. // // This particular class is really a struct so I'm allowing direct // access to the fields. Since it's private, I can play // fast and loose with the encapsulation without significantly // impacting the maintainability of the code. private class Node { public final Object subscriber; public final Node next; private Node( Object subscriber, Node next ) { this.subscriber = subscriber; this.next = next; } public Node remove( Object target ) { if( target == subscriber ) return next; if( next == null ) // target is not in list throw new java.util.NoSuchElementException (target.toString()); return new Node(subscriber, next.remove(target)); } public void accept( Distributor deliveryAgent ) // deliveryAgent is { deliveryAgent.deliverTo( subscriber ); // a "visitor" } } private volatile Node subscribers = null; /** Publish an event using the deliveryAgent. Note that this * method isn't synchronized (and doesn't have to be). Those * subscribers that are on the list at the time the publish * operation is initiated will be notified. (So, in theory, * it's possible for an object that cancels its subsciption * to nonetheless be notified.) There's no universally "good" * solution to this problem. */ public void publish( Distributor deliveryAgent ) { for(Node cursor = subscribers; cursor != null; cursor = cursor.next) cursor.accept( deliveryAgent ); } synchronized public void subscribe( Object subscriber ) { subscribers = new Node( subscriber, subscribers ); } synchronized public void cancelSubscription( Object subscriber ) { subscribers = subscribers.remove( subscriber ); } //------------------------------------------------------------------ private static class Test { static final StringBuffer actualResults = new StringBuffer(); static final StringBuffer expectedResults = new StringBuffer(); interface Observer { void notify( String arg ); } static class Notifier { private Publisher publisher = new Publisher(); public void addObserver( Observer l ) { publisher.subscribe(l); } public void removeObserver ( Observer l ) { publisher.cancelSubscription(l); } public void fire( final String arg ) { publisher.publish ( new Publisher.Distributor() { public void deliverTo( Object subscriber ) { ((Observer)subscriber).notify(arg); } } ); } } public static void main( String[] args ) { Notifier source = new Notifier(); int errors = 0; Observer listener1 = new Observer() { public void notify( String arg ) { actualResults.append( "1[" + arg + "]" ); } }; Observer listener2 = new Observer() { public void notify( String arg ) { actualResults.append( "2[" + arg + "]" ); } }; source.addObserver( listener1 ); source.addObserver( listener2 ); source.fire("a"); source.fire("b"); expectedResults.append("2[a]"); expectedResults.append("1[a]"); expectedResults.append("2[b]"); expectedResults.append("1[b]"); source.removeObserver( listener1 ); try { source.removeObserver(listener1); System.err.print("Removed nonexistant node!"); ++errors; } catch( java.util.NoSuchElementException e ) { // should throw an exception, which we'll catch // (and ignore) here. } expectedResults.append("2[c]"); source.fire("c"); if( !expectedResults.toString().equals(actualResults.toString()) ) { System.err.print("add/remove/fire failure.\n"); System.err.print("Expected:["); System.err.print( expectedResults.toString() ); System.err.print("]\nActual: ["); System.err.print( actualResults.toString() ); System.err.print("]"); ++errors; } source.removeObserver( listener2 ); source.fire("Hello World"); try { source.removeObserver( listener2 ); System.err.println("Undetected illegal removal."); ++errors; } catch( Exception e ) { /*everything's okay, do nothing*/ } if( errors == 0 ) System.err.println("com.holub.tools.Publisher: OKAY"); System.exit( errors ); } } }
Node 的设计考虑比较细致,订阅者不会接收到订阅前发布的主题。