Observer¶
The observer is one of the most important and frequently used patterns in practice. It describes how to create a one-to-many dependency in such a way that others are informed about the update of the state of one of the objects.
An example of such a pattern could be a single channel on a communicator. There can be many users on a channel, and the status update by one of them (i.e. sending messages to the channel) causes other users to get information about this fact (i.e. the sent message appears in the message window).
Construction¶
In order to build a one-to-many relationship, we need:
- interface or abstract class common to all observers
- an object that you can observe (the channel on the communicator in the example above), often called
observable
orsubject
In addition, there must be a way to:
- connect the observer with the observed object
- the observer could react to the change of state.
Example¶
The example is also based on posting a message to a channel. It consists of the following classes:
BaseObserver
- an abstract class representing theobserver
(the part of the relationship that subscribes to theobservable
). This class takes in the constructor a reference to theChatChannel
class and subscribes to it immediately (i.e. joins)UserObserver
- extends theBaseObserver
class. It reacts to sent messages from other users, but not of theADMIN
user type.AdminObserver
- it also extends theBaseObserver
class and always reacts to sent messages, regardless of user type.ChatChannel
- class representing the messenger channel. The channel uses subscriptions. It has the ability to inform all followers about the change in status.
public abstract class BaseObserver {
protected final ChatChannel chatChannel;
public BaseObserver(final ChatChannel chatChannel) {
this.chatChannel = chatChannel;
chatChannel.subscribe(this);
}
public abstract void handleMessage(String message, String userType);
public abstract String getObserverType();
public void sendMessage(final String message) {
chatChannel.sendMessage(message, getObserverType());
}
}
public class UserObserver extends BaseObserver {
private final String userName;
public UserObserver(final ChatChannel chatChannel, final String userName) {
super(chatChannel);
this.userName = userName;
System.out.println(userName + " is joining the " + chatChannel.getName());
}
@Override
public void handleMessage(final String message, final String userType) {
if (!userType.equals("ADMIN")) {
System.out.println(userName + " sees message " + message);
}
}
@Override
public String getObserverType() {
return "USER";
}
}
public class AdminObserver extends BaseObserver {
private final String adminName;
public AdminObserver(final ChatChannel chatChannel, final String adminName) {
super(chatChannel);
this.adminName = adminName;
System.out.println(adminName + " joined " + chatChannel.getName() + " as admin.");
}
@Override
public void handleMessage(final String message, final String userType) {
System.out.println(adminName + " sees " + message + " from user whose type is " + userType);
}
@Override
public String getObserverType() {
return "ADMIN";
}
}
import java.util.ArrayList;
import java.util.List;
public class ChatChannel {
private final String name;
private final List<BaseObserver> observers = new ArrayList<>();
private final List<String> messages = new ArrayList<>();
public ChatChannel(final String name) {
this.name = name;
}
public void subscribe(final BaseObserver observer) {
if (!observers.contains(observer)) {
observers.add(observer);
}
}
public void sendMessage(final String message, final String observerType) {
messages.add(message);
notifyAboutChange(message, observerType);
}
private void notifyAboutChange(final String message, final String observerType) {
for (final var observer : observers) {
observer.handleMessage(message, observerType);
}
}
public String getName() {
return name;
}
}
public class ObserverUsage {
public static void main(String[] args) {
final ChatChannel chatChannel = new ChatChannel("design-patterns");
final BaseObserver userA = new UserObserver(chatChannel, "andrew");
final BaseObserver userB = new UserObserver(chatChannel, "ala");
final BaseObserver adminA = new AdminObserver(chatChannel, "john");
final BaseObserver adminB = new AdminObserver(chatChannel, "ann");
userA.sendMessage("Hello all");
userB.sendMessage("Hi Andrew");
adminA.sendMessage("ann they can't see what we write");
adminB.sendMessage("Yes I know");
/* output of the program:
andrew is joining the design-patterns
ala is joining the design-patterns
john joined design-patterns as admin.
ann joined design-patterns as admin.
andrew sees message Hello all
ala sees message Hello all
john sees Hello all from user whose type is USER
ann sees Hello all from user whose type is USER
andrew sees message Hi Andrew
ala sees message Hi Andrew
john sees Hi Andrew from user whose type is USER
ann sees Hi Andrew from user whose type is USER
john sees ann they can't see what we write from user whose type is ADMIN
ann sees ann they can't see what we write from user whose type is ADMIN
john sees Yes I know from user whose type is ADMIN
ann sees Yes I know from user whose type is ADMIN
*/
}
}