I've finished an implementation of what I believe the Publish-Subscribe pattern is for practice and possible use in personal projects even though this implementation is fairly similar to the Observer/Observable classes of the Java API.
I'm looking for a review of my code-style, JavaDoc usage, API usage, implementation of the pattern, tests, and just about anything else you can think of.
Radio:
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Set;
public class Radio <Data> {
/** A {@link HashMap} of events mapped to listening {@link Receiver Receivers}. */
private final HashMap<String, Set<Receiver<Data>>> receivers = new HashMap<>();
/**
* Transmits an event without data.
*
* @param event
* The event to transmit.
*/
public final void transmit(final String event) {
if (event == null || event.isEmpty()) {
throw new NullPointerException("A transmitted event cannot be null or empty.");
}
transmit(event, null);
}
/**
* Transmits an event with data.
*
* @param event
* The event whose receivers are to be transmitted to.
*
* @param data
* The data to transmit to the {@link Receiver Receivers}.
*/
public final void transmit(final String event, final Data data) {
if (event == null || event.isEmpty()) {
throw new NullPointerException("The event cannot be null or empty.");
}
final Set<Receiver<Data>> receivers = this.receivers.get(event);
if (receivers != null) {
receivers.forEach(receiver -> receiver.receive(event, data));
}
}
/**
* Adds a receiver to an event.
*
* @param event
* The event to add a receiver to.
*
* @param receiver
* The receiver to add.
*/
public final void addReceiver(final String event, final Receiver<Data> receiver) {
if (event == null || event.isEmpty()) {
throw new NullPointerException("The event cannot be null or empty.");
}
if (receiver == null) {
throw new NullPointerException("The receiver cannot be null.");
}
if (receivers.containsKey(event) == false) {
receivers.put(event, new LinkedHashSet<>());
}
receivers.get(event).add(receiver);
}
/**
* Removes a receiver from an event.
*
* @param event
* The event to remove a receiver from.
*
* @param receiver
* The receiver to remove.
*/
public final void removeReceiver(final String event, final Receiver<Data> receiver) {
if (event == null || event.isEmpty()) {
throw new NullPointerException("The event cannot be null or empty.");
}
if (receiver == null) {
throw new NullPointerException("The receiver cannot be null.");
}
final Set<Receiver<Data>> receivers = this.receivers.get(event);
if (receivers != null) {
receivers.remove(receiver);
}
}
/**
* Removes all receivers from an event.
*
* @param event
* The event to remove receivers from.
*/
public final void removeReceivers(final String event) {
if (event == null || event.isEmpty()) {
throw new NullPointerException("The event cannot be null or empty.");
}
final Set<Receiver<Data>> receivers = this.receivers.get(event);
if (receivers != null) {
receivers.clear();
}
}
}
Receiver:
public interface Receiver<Data> {
/**
* Receives data from a transmission.
*
* @param event
* The event that caused the transmission.
*
* @param data
* The received data.
*/
void receive(final String event, final Data data);
}
RadioTest:
import org.junit.Assert;
import org.junit.Test;
public class RadioTest {
@Test
public void transmitA() {
final Radio<String> radio = new Radio<>();
final ReceiverTest receiver = new ReceiverTest();
radio.addReceiver("Test Event", receiver);
radio.transmit("Test Event");
Assert.assertEquals(receiver.getData(), null);
}
@Test
public void transmitB() {
final Radio<String> radio = new Radio<>();
final ReceiverTest receiver = new ReceiverTest();
radio.addReceiver("Test Event", receiver);
radio.transmit("Test Event", null);
Assert.assertEquals(receiver.getData(), null);
}
@Test
public void transmitC() {
final Radio<String> radio = new Radio<>();
final ReceiverTest receiver = new ReceiverTest();
radio.addReceiver("Test Event", receiver);
radio.transmit("Test Event", "Hello");
Assert.assertEquals(receiver.getData(), "Hello");
}
@Test
public void transmitD() {
final Radio<String> radio = new Radio<>();
final ReceiverTest receiver = new ReceiverTest();
radio.addReceiver("Test Event", receiver);
radio.transmit("Other Event", "Hello");
Assert.assertEquals(receiver.getData(), "");
}
@Test(expected=NullPointerException.class)
public void removeReceiverA() {
final Radio<String> radio = new Radio<>();
radio.removeReceiver(null, null);
}
@Test(expected=NullPointerException.class)
public void removeReceiverB() {
final Radio<String> radio = new Radio<>();
radio.removeReceiver("Test Event", null);
}
@Test
public void removeReceiverC() {
final Radio<String> radio = new Radio<>();
final ReceiverTest receiver = new ReceiverTest();
radio.removeReceiver("Test Event", receiver);
}
@Test
public void removeReceiverD() {
final Radio<String> radio = new Radio<>();
final ReceiverTest receiver = new ReceiverTest();
radio.addReceiver("Test Event", receiver);
radio.removeReceiver("Test Event", receiver);
}
@Test(expected=NullPointerException.class)
public void removeReceiversA() {
final Radio<String> radio = new Radio<>();
radio.removeReceivers(null);
}
@Test
public void removeReceiversB() {
final Radio<String> radio = new Radio<>();
radio.removeReceivers("Test Event");
}
@Test
public void removeReceiversC() {
final Radio<String> radio = new Radio<>();
final ReceiverTest receiver = new ReceiverTest();
radio.addReceiver("Test Event", receiver);
radio.removeReceivers("Test Event");
}
}
ReceiverTest:
public class ReceiverTest implements Receiver<String> {
private String data = "";
@Override
public void receive(final String event, final String data) {
this.data = data;
}
public String getData() {
return data;
}
}