SE450: Subclassing: Designing for Delegation [3/35] Previous pageContentsNext page

file:stdlib/SubscriptionsTEST.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package stdlib;

import static org.junit.Assert.*;
import org.junit.Test;

public class SubscriptionsTEST {
  public static class Counter {
    private Subscriptions<Counter,String> oh = new Subscriptions<Counter,String>(this);
    public void addSubscriber(Subscriber<Counter,String> subscriber) {
      oh.addSubscriber(subscriber);
    }
    public void notifySubscribers(String data) {
      oh.notifySubscribers(data);
    }

    int j;
    public int get() {
      return j;
    }
    public void inc() {
      j++;
      oh.setChanged();
    }
    public String toString() {
      return "Counter(" + j + ")";
    }
  }

  @Test
  public void testA () {
    Counter c = new Counter();
    c.addSubscriber((sender, data) -> System.out.println ("update(" + sender + "," + data + ")"));
    c.inc();
    c.inc();
    c.notifySubscribers("Dog");
  }
  private String output;
  @Test
  public void testB () {
    Counter c = new Counter();
    c.addSubscriber((sender, data) -> output = ("update(" + sender + "," + data + ")"));
    c.inc();
    c.inc();
    c.notifySubscribers("Dog");
    assertEquals("update(Counter(2),Dog)",output);
  }
}

file:stdlib/Subscriber.java [source] [doc-public] [doc-private]
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
package stdlib;
/**
 * A generic subscriber which a class can implement when it wants to be informed
 * of changes in published objects.
 *
 * @param <T> the type of the publisher object
 * @param <U> the type of the optional data argument
 * @see Subscriptions
 */
public interface Subscriber<T, U> {
  /**
   * This method is called whenever the published object is changed.
   *
   * @param publisher the object which was the source of the notification.
   * @param data an optional data parameter which encapsulates any
   *   additional data about the event
   */
  void update(T publisher, U data);
}

file:stdlib/Subscriptions.java [source] [doc-public] [doc-private]
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package stdlib;
import java.util.ArrayList;
import java.util.List;

/**
 * A helper class which can be used to manage an object's subscriptions.
 *
 * @param <T> the type of the publisher
 * @param <U> the type of the optional data argument
 * @see Subscriber
 */
public final class Subscriptions<T, U> {
  private final T publisher;
  private final List<Subscriber<T, U>> subscribers;
  private boolean changed = false;

  /**
   * Initializes a new instance of the {@link Subscriptions} class with
   * zero subscribers.
   *
   * @param publisher the object being publisher
   * @throws IllegalArgumentException if publisher is <code>null</code>
   */
  public Subscriptions(T publisher) {
    if (publisher == null)
      throw new IllegalArgumentException("publisher cannot be null");
    this.publisher = publisher;
    this.subscribers = new ArrayList<Subscriber<T, U>>();
  }

  /**
   * Adds an subscriber to the set of subscribers for the publisher object,
   * provided that it is not the same as some subscriber already in the set.
   *
   * @param subscriber
   * @throws IllegalArgumentException if the parameter subscriber is <code>null</code>
   */
  public void addSubscriber(Subscriber<T, U> subscriber) {
    if (subscriber == null)
      throw new IllegalArgumentException("subscriber cannot be null");
    if (!subscribers.contains(subscriber)) {
      subscribers.add(subscriber);
    }
  }

  /**
   * Indicates that the publisher object has no longer changed, or that it has
   * already notified all of its subscribers of its most recent change, so that
   * {@link #hasChanged()} will now return <code>false</code>. This method
   * is called automatically by the {@link #notifySubscribers} methods.
   *
   */
  public void clearChanged() {
    changed = false;
  }

  /**
   * Returns the number of subscribers of publisher object.
   *
   * @return the number of subscribers of publisher object
   */
  public int countSubscribers() {
    return subscribers.size();
  }

  /**
   * Deletes an subscriber from the set of subscribers. Passing <code>null</code>
   * to this method will have no effect.
   *
   * @param subscriber the {@link Subscriber} to be deleted
   */
  public void deleteSubscriber(Subscriber<T, U> subscriber) {
    subscribers.remove(subscriber);
  }

  /**
   * Clears the subscriber list so that the publisher object no longer has any
   * subscribers.
   */
  public void deleteSubscribers() {
    this.subscribers.clear();
  }

  /**
   * Returns <code>true</code> if the publisher object has changed;
   * otherwise, <code>false</code>.
   *
   * @return <code>true</code> if the publisher object has changed;
   *         otherwise, <code>false</code>
   */
  public boolean hasChanged() {
    return changed;
  }

  /**
   * If this object has changed, as indicated by the {@link #hasChanged()},
   * then notify all of its subscribers and then clear the changed state. This
   * method is equivalent to calling <code>notifySubscribers(null)</code>.
   */
  public void notifySubscribers() {
    notifySubscribers(null);
  }

  /**
   * If this object has changed, as indicated by the {@link #hasChanged()},
   * then notify all of its subscribers and then clear the changed state.
   *
   * @param data optional event specific data which will be passed to the subscribers
   */
  public void notifySubscribers(U data) {
    if (hasChanged()) {
      for (Subscriber<T, U> subscriber : subscribers) {
        subscriber.update(publisher, data);
      }
      clearChanged();
    }
  }

  /**
   * Flags the publisher object as having changed.
   */
  public void setChanged() {
    changed = true;
  }
}

Disadvantage of subclassing in Java: must export interface of superclass.

Advantage of subclassing in Java: syntactically light, easy to share fields.

Previous pageContentsNext page