Search code examples
javadesign-patternsobservers

The best way to have multiple observer lists in observer pattern


I'm using observer pattern to write a simple group chat program.

Group1: A, B, and C Group2: A and C

A is a sender/subject with two lists, one for Group1 and one for Group2.

Is having two lists in subject class a good way to do this?

Is this violating observer pattern's feature?

A sender can have two groups of observers.

public class Sender {

    private List<Receiver> group1 = new ArrayList<Receiver>();
    private List<Receiver> group2 = new ArrayList<Receiver>();
    private String msg;
    private String name;

    public Sender(String name) {
        this.name = name;
    }

    public void sendMsg(int group, String msg, JTextArea display) {
        this.msg = msg;
        String output = name + ": " + msg;
        display.append(output + "\n\r");
        if(group == 1) {
            notifyAllObservers(group1);
        } else {
            notifyAllObservers(group2);
        }

    }

    public void register(int group, Receiver receiver) {
        if(group == 1) {
            group1.add(receiver);
        } else {
            group2.add(receiver);
        }

    }

    public void notifyAllObservers(List<Receiver> group) {
        for (Receiver receiver : group) {
            receiver.update(msg);
        }
    }

    public String toString() {
        return name;
    }

}

public class Receiver {

    public Sender sender;
    private JTextArea display;
    public Receiver(int group, Sender sender, JTextArea display) {
        this.sender = sender;
        this.display = display;
        this.sender.register(group, this);
    }

    public void update(String msg) {
        display.append(sender.toString() + ": " + msg + "\n\r");
    }
}

Solution

  • Your current design is not necessarily contradicting the Observer pattern, but it is rigid. As of now, every Sender can be in up to two groups. What if every sender should be in up to 10 groups? 100 groups? To keep this design flexible, I propose to model Groups as objects aswell. The idea is that every Group is an Observable and an Observer at the same time. Each Group has a List<Sender> senders, to which it automatically registers as Observer. If a Group receives some Event from one of the Observables it is registered to, it will forward this event to its Observers. The following code is a rough sketch of my proposal.

    public interface Observer {
      public void receiveEvent(Observable source, Event event);
    }
    
    public interface Observable {
      public void addObserver(Observer observer);
      public void removeObserver(Observer observer);
      public Collection<Observer> getObservers();
    
      default public void notifyAllObservers(Event event) {
        for (Observer observer : this.getObservers()) {
          observer.receiveEvent(this, event);
        }
      }
    }
    
    public interface Event { }
    
    public abstract class AbstractObservableImpl implements Observable {
      private Set<Observer> observers = new HashSet<>();
    
      @Override
      public final void addObserver(final Observer observer) {
          this.observers.add(observer);
      }
    
      @Override
      public final void removeObserver(final Observer observer) {
        this.observers.remove(observer);
      }
    
      @Override
      public final Collection<Observer> getObservers() {
        return Collections.unmodifiableCollection(this.observers);
      }
    }
    
    public class Sender extends AbstractObservableImpl { }
    
    public class Group extends AbstractObservableImpl implements Observer {
      private List<Sender> senders = new ArrayList<>();
    
      @Override
      public final void receiveEvent(final Observable source, final Event event) {
        for (Observer observer : this.getObservers()) {
          observer.receiveEvent(this, event);
        }
      }
    
      public final void addSender(Sender sender) {
        if (this.senders.contains(sender) == false) {
          this.senders.add(sender);
        }
        this.senders.get(this.senders.indexOf(sender)).addObserver(this);
      }
    
      public final void removeSender(Sender sender) {
        final int index = this.senders.indexOf(sender));
        if (index >= 0)  {
          this.senders.get(index).removeObserver(this);
        }
        this.senders.remove(sender);
      }
    }
    

    Some remarks on the design:

    • The abstract class AbstractObserverImpl is not essential. I was just too lazy to repeat code and since Sender and Group do not inherit from anything else, I let them inherit from AbstractObserverImpl.
    • It is also not essential that AbstractObserverImpl is abstract. For me, there would be no point in allowing instantiation of this class since it is missing its actual functionality (the part firing the Events).
    • I opted to create an empty Event interface. This is arbirtrary aswell. Whether you use Objects as events, or enums, or an interface, or a class or a different approach is totally up to you. As I said: this is only a rough sketch.
    • The sketch is not null-safe. There are quite some possibilites to cause NullPointerExceptions.