Welcome back! We're continuing our exploration of Behavioral Patterns. In this lesson, we will delve into the Observer Pattern, another fundamental pattern that emphasizes object communication and responsibility distribution. This pattern allows an object, known as the subject
, to maintain a list of its dependents, called observers
, and notify them automatically of any state changes, usually by calling one of their methods.
Previously, we looked at the Command Pattern, which encapsulates a request as an object. Now, let's build on that knowledge and explore how the Observer Pattern facilitates communication between objects in a seamless and efficient manner.
Let's break down the process into clear steps to implement the Observer Pattern.
Start by defining the Observer
class that lays out the interface for receiving updates.
JavaScript1class Subscriber { 2 update(news) { 3 throw new Error('You have to implement the method update!'); 4 } 5}
In this code snippet, the Subscriber
class defines the update
method. This method will be called by the subject to notify subscribers of any updates.
Next, create the NewsPublisher
class, which acts as the Subject
. This class maintains a list of subscribers and provides methods to add and remove subscribers, as well as to notify them.
JavaScript1class NewsPublisher { 2 constructor() { 3 this.subscribers = []; 4 } 5 6 addSubscriber(subscriber) { 7 this.subscribers.push(subscriber); 8 } 9 10 removeSubscriber(subscriber) { 11 const index = this.subscribers.indexOf(subscriber); 12 if (index !== -1) { 13 this.subscribers.splice(index, 1); 14 } 15 } 16 17 publish(news) { 18 this.subscribers.forEach(subscriber => subscriber.update(news)); 19 } 20}
The NewsPublisher
class has three methods: addSubscriber
to add a new subscriber, removeSubscriber
to remove an existing subscriber, and publish
to notify all subscribers of new news.
Now, implement a concrete observer, which in this case will be a specific type of subscriber. This observer will define how to handle the updates received from the subject.
JavaScript1class ConcreteSubscriber extends Subscriber { 2 constructor(name) { 3 super(); 4 this.name = name; 5 } 6 7 update(news) { 8 console.log(`${this.name} received news: ${news}`); 9 } 10}
The ConcreteSubscriber
class extends the Subscriber
base class and implements the update
method to print the news received.
Finally, see how these components work together in a script execution context, which serves as the client in this pattern.
JavaScript1const main = () => { 2 const newsPublisher = new NewsPublisher(); 3 const subscriber1 = new ConcreteSubscriber("Subscriber 1"); 4 const subscriber2 = new ConcreteSubscriber("Subscriber 2"); 5 6 newsPublisher.addSubscriber(subscriber1); 7 newsPublisher.addSubscriber(subscriber2); 8 9 newsPublisher.publish("Breaking News 1"); 10 // Output: 11 // Subscriber 1 received news: Breaking News 1 12 // Subscriber 2 received news: Breaking News 1 13 14 newsPublisher.removeSubscriber(subscriber1); 15 newsPublisher.publish("Breaking News 2"); 16 // Output: 17 // Subscriber 2 received news: Breaking News 2 18} 19 20main();
In the main
function, an instance of NewsPublisher
is created along with two ConcreteSubscriber
objects. These subscribers are added to the publisher's list, and when the publisher's publish
method is called, all subscribers receive notifications of the news. After demonstrating the notification process, one subscriber is removed, and a second news publication is executed to show that only the remaining subscriber receives the update, illustrating the dynamic nature of the Observer Pattern.
Here is the complete code put together:
JavaScript1class Subscriber { 2 update(news) { 3 throw new Error('You have to implement the method update!'); 4 } 5} 6 7class NewsPublisher { 8 constructor() { 9 this.subscribers = []; 10 } 11 12 addSubscriber(subscriber) { 13 this.subscribers.push(subscriber); 14 } 15 16 removeSubscriber(subscriber) { 17 const index = this.subscribers.indexOf(subscriber); 18 if (index !== -1) { 19 this.subscribers.splice(index, 1); 20 } 21 } 22 23 publish(news) { 24 this.subscribers.forEach(subscriber => subscriber.update(news)); 25 } 26} 27 28class ConcreteSubscriber extends Subscriber { 29 constructor(name) { 30 super(); 31 this.name = name; 32 } 33 34 update(news) { 35 console.log(`${this.name} received news: ${news}`); 36 } 37} 38 39const main = () => { 40 const newsPublisher = new NewsPublisher(); 41 const subscriber1 = new ConcreteSubscriber("Subscriber 1"); 42 const subscriber2 = new ConcreteSubscriber("Subscriber 2"); 43 44 newsPublisher.addSubscriber(subscriber1); 45 newsPublisher.addSubscriber(subscriber2); 46 47 newsPublisher.publish("Breaking News 1"); 48 // Output: 49 // Subscriber 1 received news: Breaking News 1 50 // Subscriber 2 received news: Breaking News 1 51 52 newsPublisher.removeSubscriber(subscriber1); 53 newsPublisher.publish("Breaking News 2"); 54 // Output: 55 // Subscriber 2 received news: Breaking News 2 56} 57 58main();
Mastering the Observer Pattern is crucial for designing systems where objects need to maintain synchronized states. This pattern promotes loose coupling, enhances code readability, and improves maintainability.
Consider a news publishing system where multiple subscribers (users) receive updates whenever new articles are published. The Observer Pattern ensures that all subscribers are automatically notified of the new news without the publisher needing to maintain direct dependencies on each subscriber. This design greatly simplifies future system extensions, as new subscriber types can be added without modifying existing code.
The Observer Pattern unlocks the ability to create highly responsive and well-structured software systems. Let's move on to the practice section and see these concepts in action!