Observer is a behavioral design pattern:
🖤🖤🖤🖤🖤
What is Observer?
Observer is a technique that defines a teacher-students
one-to-many dependency between objects so that when one object changes state, all its dependents are notified and reacted automatically . It is also referred to as the publish-subscribe pattern.
Why use Observer?
- It defines a subscription mechanism to notify objects about events of the observed object.
- It supports loose coupling between the subject and observers. The subject only knows a common observer interface and ignores concrete observers.
- It makes the code modular, easy to maintain, allows adding new observers anytime without modifying the subject or others.
Question: I hate the Observer, what are the alternatives?
Answer: Other behavioral patterns such as Mediator, Chain of Responsibility, or State may suit your problem and context better. For example, Mediator reduces direct communication between components by making them communicate via a mediator object. Chain of Responsibility passes requests along a chain of handlers until one handles it. State changes an object’s behavior when its state changes. You can also code without using any pattern.
The State pattern lets an object change its behavior based on its internal state. It promotes loose coupling by putting the behavior in state objects. This lets the object change its behavior at runtime by changing its state. The State pattern is useful when an object needs different behaviors for different states.
The Observer pattern sets up a one-to-many dependency between objects. It notifies multiple observers when the observed object changes its state. It promotes loose coupling by making the observed object and observers unaware of each other. This allows easy addition or removal of observers without changing the observed object’s interface. The Observer pattern is useful when one object’s changes affect many others.
In summary, while both patterns deal with changing an object’s behavior in response to a change in its state, the State pattern deals with an object’s internal state, while the Observer pattern deals with external changes in the observed object’s state.
When to use Observer?
Question: When do I use Observer?
Answer: When you have situations like these:
- need to update several objects when another object changes its state.
- avoid tight coupling between an object and its dependents while still allowing them to communicate effectively.
- broadcast events to multiple subscribers without knowing who they are or how many there are.
1
2
3
4
5
6
7
8
9
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
|
class Teacher:
def __init__(self) -> None:
self._state: str = None
@property
def state(self) -> str:
return self._state
@state.setter
def state(self, value: str) -> None:
self._state = value
def act(self, action: str) -> None:
"""Change the state of the teacher."""
self.state = action
print(f"- TEACHER ACT: I'm {self.state}")
# A class that represents a bad student who gossips when the teacher goes out
class BadStudent:
def react(self, teacher: Teacher) -> None:
if teacher.state == "going out":
print(f" - BAD_STUDENT REACT: 😃 I'm gossiping in the class")
else:
print(f" - BAD_STUDENT REACT: I don't care!")
# A class that represents a good student who focuses when the teacher speaks
class GoodStudent:
def react(self, teacher: Teacher) -> None:
if teacher.state == "speaking":
print(f" - GOOD_STUDENT REACT: 😃 I'm focusing on what the teacher says")
else:
print(f" - GOOD_STUDENT REACT: I don't care!")
|
Expected Output:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
Teacher said: Hello new student '99' to my class
Teacher said: Hello new student '78' to my class
- TEACHER ACT: I'm replying husband's SMS
- BAD_STUDENT REACT: I don't care!
- GOOD_STUDENT REACT: I don't care!
- TEACHER ACT: I'm going out
- BAD_STUDENT REACT: 😃 I'm gossiping in the class
- GOOD_STUDENT REACT: I don't care!
- TEACHER ACT: I'm speaking
- BAD_STUDENT REACT: I don't care!
- GOOD_STUDENT REACT: 😃 I'm focusing on what the teacher says
Teacher said: GoodBye student '78'
- TEACHER ACT: I'm going out
- GOOD_STUDENT REACT: I don't care!
|
How to implement Observer?
Non-Observer implementation:
1
2
3
4
5
6
7
8
9
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
48
49
50
51
52
53
54
55
56
57
58
59
60
|
# A class that represents a teacher who can change their state
class Teacher:
def __init__(self) -> None:
self._state: str = None
@property
def state(self) -> str:
return self._state
@state.setter
def state(self, value: str) -> None:
self._state = value
def act(self, action: str) -> None:
"""Change the state of the teacher."""
self.state = action
print(f"- TEACHER ACT: I'm {self.state}")
# A class that represents a bad student who gossips when the teacher goes out
class BadStudent:
def react(self, teacher: Teacher) -> None:
if teacher.state == "going out":
print(f" - BAD_STUDENT REACT: 😃 I'm gossiping in the class")
else:
print(f" - BAD_STUDENT REACT: I don't care!")
# A class that represents a good student who focuses when the teacher speaks
class GoodStudent:
def react(self, teacher: Teacher) -> None:
if teacher.state == "speaking":
print(f" - GOOD_STUDENT REACT: 😃 I'm focusing on what the teacher says")
else:
print(f" - GOOD_STUDENT REACT: I don't care!")
if __name__ == "__main__":
# Create a teacher and some students
teacher = Teacher()
bad_student = BadStudent()
good_student = GoodStudent()
# The teacher changes their state and the students react accordingly
teacher.act("replying husband's SMS")
bad_student.react(teacher)
good_student.react(teacher)
teacher.act("going out")
bad_student.react(teacher)
good_student.react(teacher)
teacher.act("speaking")
bad_student.react(teacher)
good_student.react(teacher)
teacher.act("going out")
good_student.react(teacher)
|
- This code violates the
open-closed
principle, which declares that classes should be open for extension but closed for modification.
- In this case: If we want to add more types of students or more actions for the teacher, we have to modify the existing code!
- It does not follow the
don’t repeat yourself
(DRY) principle, which declares that duplication of code should be avoided.
- In this case: The student’s classes have to repeat the same logic for checking the teacher’s state and reacting to it, which makes them prone to errors and inconsistencies.
- Using the Observer pattern can overcome these limitations by decoupling the subjects (teachers) from their observers (students), allowing them to communicate through an abstract interface (the Student abstract class), and making them independent of each other’s implementation details. This way, we can add new types of students or new actions for teachers without changing existing code or introducing new dependencies. We can also avoid duplication of code by implementing common logic in abstract classes or methods.
Observer Implementation:
1
2
3
4
5
6
7
8
9
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
|
from __future__ import annotations
from abc import ABC, abstractmethod
class Teacher:
"""A class that represents a teacher who can change their state and notify their students."""
def __init__(self) -> None:
self._state: str = None
self._students: list[Student] = []
@property
def state(self) -> str:
return self._state
@state.setter
def state(self, value: str) -> None:
self._state = value
@property
def students(self) -> list[Student]:
return self._students
def attach(self, student: Student) -> None:
"""Add a new student to the list of observers."""
print(f"Teacher said: Hello new student '{id(student)}' to my class")
self.students.append(student)
def detach(self, student: Student) -> None:
"""Remove a student from the list of observers."""
print(f"Teacher said: GoodBye student '{id(student)}'")
self.students.remove(student)
def notify_students(self) -> None:
"""Notify all the students about the current state of the teacher."""
print(f"- TEACHER ACT: I'm {self.state}")
for student in self.students:
student.react(self)
def act(self, action: str) -> None:
"""Change the state of the teacher and notify the students."""
self.state = action
self.notify_students()
class Student(ABC):
"""An abstract class that represents a student who can react to a teacher's state."""
@abstractmethod
def react(self, teacher: Teacher) -> None:
pass
class BadStudent(Student):
"""A class that represents a bad student who gossips when the teacher goes out."""
def react(self, teacher: Teacher) -> None:
if teacher.state == "going out":
print(f" - BAD_STUDENT REACT: 😃 I'm gossiping in the class")
else:
print(f" - BAD_STUDENT REACT: I don't care!")
class GoodStudent(Student):
"""A class that represents a good student who focuses when the teacher speaks."""
def react(self, teacher: Teacher) -> None:
if teacher.state == "speaking":
print(f" - GOOD_STUDENT REACT: 😃 I'm focusing on what the teacher says")
else:
print(f" - GOOD_STUDENT REACT: I don't care!")
if __name__ == "__main__":
# Create a teacher object
teacher = Teacher()
# Create two student objects
bad_student = BadStudent()
good_student = GoodStudent()
# Attach both students to observe the teacher
teacher.attach(bad_student)
teacher.attach(good_student)
# Perform some actions as a teacher
teacher.act("replying husband's SMS")
teacher.act("going out")
teacher.act("speaking")
# Detach one student from observing the teacher
teacher.detach(bad_student)
# Perform another action as a teacher
teacher.act("going out")
|
The improvement of this code compared to the non-observer code is that it follows some design principles that make the code more maintainable, extensible and reusable. Some of these principles are:
- The
open-closed
principle: This code allows us to add new types of students or new actions for teachers without changing existing code or introducing new dependencies.
- The
don’t repeat yourself
(DRY) principle: This code avoids repeating the same logic for checking the teacher’s state and reacting to it by implementing common logic in abstract classes or methods.
Source Code