Skip to content

Observer

Observer is one of the most important and more frequently used patterns in practice. Describes how to create a one-to-many relationship in such a way that the others are informed about updating the state of one object.

An example of such a pattern is a single channel on a communicator. There can be many users on a channel, and a status update by one of them (i.e. sending a message to the channel) causes other users to receive information about this fact (sent message appears in the message window).

observer

Design

In order to build a one-to-many relationship, we need:

  • interface common to all followers (so-called observer)
  • an object (messenger channel in the example above), often called observable or subject

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 - the class representing observer (that is, the part of the relationship that subscribes to observable). This class takes a reference to the ChatChannel class in the constructor and subscribes to it immediately (joins)
  • UserObserver - extends the BaseObserver class. It reacts to sent messages from other users, but not of the ADMIN type.
  • AdminObserver - it also extends the BaseObserver class and always responds to sent messages, regardless of user type.
  • ChatChannel - class representing the messenger channel. The channel has the ability to subscribe to it and inform all followers about the change in state.
class BaseObserver:
    def __init__(self, chat_channel):
        self._chat_channel = chat_channel
        chat_channel.subscribe(self)

    def handle_message(self, message, user_type):
        pass

    def get_observer_type(self):
        pass

    def send_message(self, message):
        self._chat_channel.send_message(message, self.get_observer_type())
class UserObserver(BaseObserver):
    def __init__(self, chat_channel, user_name):
        super().__init__(chat_channel)
        self._user_name = user_name
        print(f"User {self._user_name} is joining the {chat_channel.get_name()}")

    def handle_message(self, message, user_type):
        if user_type != 'ADMIN':
            print(f"{self._user_name} sees message {message}")

    def get_observer_type(self):
        return "USER"
class AdminObserver(BaseObserver):
    def __init__(self, chat_channel, admin_name):
        super().__init__(chat_channel)
        self._admin_name = admin_name
        print(f"{self._admin_name} joined {chat_channel.get_name()} as admin")

    def handle_message(self, message, user_type):
        print(f"{self._admin_name} sees {message} from user whose type is {user_type}")

    def get_observer_type(self):
        return "ADMIN"
class ChatChannel:
    def __init__(self, name):
        self._name = name
        self._observers = []
        self._messages = []

    def subscribe(self, observer):
        if observer not in self._observers:
            self._observers.append(observer)

    def send_message(self, message, observer_type):
        self._messages.append(message)
        self.notify_about_change(message, observer_type)

    def notify_about_change(self, message, observer_type):
        for observer in self._observers:
            observer.handle_message(message, observer_type)

    def get_name(self):
        return self._name
def main():
    chat_channel = ChatChannel("design-patterns")

    user_a = UserObserver(chat_channel, "Andrew")
    user_b = UserObserver(chat_channel, "Alice")
    admin_a = AdminObserver(chat_channel, "John")
    admin_b = AdminObserver(chat_channel, "Anna")

    user_a.send_message("Hello all")
    user_b.send_message("Hi Andrew")
    admin_a.send_message("Anna, they can't see what we write")
    admin_b.send_message("Yes, I know")


if __name__ == '__main__':
    main()
Program output:
User Andrew is joining the design-patterns
User Alice is joining the design-patterns
John joined design-patterns as admin
Anna joined design-patterns as admin
Andrew sees message Hello all
Alice sees message Hello all
John sees Hello all from user whose type is USER
Anna sees Hello all from user whose type is USER
Andrew sees message Hi Andrew
Alice sees message Hi Andrew
John sees Hi Andrew from user whose type is USER
Anna sees Hi Andrew from user whose type is USER
John sees Anna, they can't see what we write from user whose type is ADMIN
Anna sees Anna, they can't see what we write from user whose type is ADMIN
John sees Yes, I know from user whose type is ADMIN
Anna sees Yes, I know from user whose type is ADMIN