DEV Community

Cover image for Mastering Real-Time Magic: The Observer Pattern
Nicolas B.
Nicolas B.

Posted on • Edited on

Mastering Real-Time Magic: The Observer Pattern

What is the Observer Pattern ?

At its core, the Observer pattern establishes a one-to-many dependency between objects so that when one object (the subject) changes state, all its dependents (observers) are notified and updated automatically. This pattern is ideal for scenarios where an object's state change should trigger specific actions in other parts of the code.

Observer pattern schema


Real use case : Real-Time Dashboard

The Observable Class

class Observable {
  constructor() {
    this.observers = [];
  }

  subscribe(callback) {
    this.observers.push(callback);
  }

  unsubscribe(callback) {
    this.observers = this.observers.filter(observer => observer !== callback);
  }

  notify(data) {
    this.observers.forEach(observer => observer(data));
  }
}

export default Observable;
Enter fullscreen mode Exit fullscreen mode

In this implementation, we've defined an Observable class with methods to subscribe, unsubscribe, and notify observers.

Integrating the Observer Pattern

Now, let's use the Observer pattern to create a real-time dashboard with three data sources: stock prices, weather updates, and news alerts.

Stock Price

const stockObserver = new Observable();

function updateStockPrices(price) {
  console.log(`Stock Price: $${price}`);
}

stockObserver.subscribe(updateStockPrices);

// Simulate real-time updates
setInterval(() => {
  const newPrice = Math.random() * 100;
  stockObserver.notify(newPrice);
}, 2000);
Enter fullscreen mode Exit fullscreen mode

Weather Updates

const weatherObserver = new Observable();

function updateWeather(weatherData) {
  console.log(`Weather Update: ${weatherData}`);
}

weatherObserver.subscribe(updateWeather);

// Simulate real-time updates
setInterval(() => {
  const newWeather = 'Sunny';
  weatherObserver.notify(newWeather);
}, 3000);
Enter fullscreen mode Exit fullscreen mode

News Alerts

const newsObserver = new Observable();

function updateNews(news) {
  console.log(`News Alert: ${news}`);
}

newsObserver.subscribe(updateNews);

// Simulate real-time updates
setInterval(() => {
  const headlines = ['Breaking News 1', 'Breaking News 2', 'Breaking News 3'];
  const randomNews = headlines[Math.floor(Math.random() * headlines.length)];
  newsObserver.notify(randomNews);
}, 5000);
Enter fullscreen mode Exit fullscreen mode

By implementing the Observer pattern, we've achieved a real-time dashboard that updates stock prices, weather conditions, and news alerts as soon as new data is available.


Going further with a more complex example

// Subject - Blog class
class Blog {
  constructor() {
    this.subscribers = [];
  }

  // add a subscriber (observer)
  subscribe(subscriber) {
    this.subscribers.push(subscriber);
  }

  // remove a subscriber (observer)
  unsubscribe(subscriberToRemove) {
    this.subscribers = this.subscribers.filter(subscriber => subscriber !== subscriberToRemove);
  }

  // Notify when there is a new article
  publishNewArticle(article) {
    this.subscribers.forEach(subscriber => {
      subscriber.notify(article);
    });
  }
}

// Observer - Subscriber class
class Subscriber {
  constructor(name) {
    this.name = name;
  }

  // notification method
  notify(article) {
    console.log(`${this.name} got a notification : new article! - "${article}"`);
  }
}

// Let's create a blog
const myBlog = new Blog();

// With three random users
const subscriber1 = new Subscriber('Alice');
const subscriber2 = new Subscriber('Bob');
const subscriber3 = new Subscriber('Charlie');

// The subscribers join to the blog
myBlog.subscribe(subscriber1);
myBlog.subscribe(subscriber2);
myBlog.subscribe(subscriber3);

// New article!
myBlog.publishNewArticle('Les dernières tendances en programmation');

// Result : Each subscriber got a notification

// Unsubscribe a user
myBlog.unsubscribe(subscriber2);

// New article!
myBlog.publishNewArticle('Guide de démarrage rapide en développement web');

// Result : Only Alice AND Charlie got a notification.
Enter fullscreen mode Exit fullscreen mode

Benefits of the Observer Pattern

1 - Real-Time Updates: The Observer pattern enables real-time updates across various parts of your application, providing a seamless and responsive user experience.

2 - Decoupled Code: Objects are loosely coupled, making it easy to add or remove observers without affecting other parts of the code.

3 - Customizable Reactions: Observers can define custom reactions to changes, allowing for flexibility and extensibility.


Conclusion

The Observer pattern is a valuable addition to your JavaScript toolkit, particularly when you need real-time updates and efficient communication between components. As demonstrated through our real-time dashboard example, it ensures that changes in one part of your application immediately affect other dependent parts, resulting in a more interactive and dynamic user experience.

Top comments (9)

Collapse
 
cscarlson profile image
cScarlson

Glad to see people leveraging this wonderful pattern. That said, there are some deviations from the pattern's specification the author took that I believe I should point out and there are some deviations I would recommend that the author did not implement.

THE AUTHOR'S DEVIATIONS

The author called The Observer Pattern's main class "Observable" whereas in The Observer Pattern specification the Participant is called "Subject".

The author also took the liberty of renaming the "attach" & "detach" methods to "subscribe" & "unsubscribe" (respectively).

All of these deviations are not bad unless you'd like to have easy identification of which pattern is being used where because, in this case, it could be confused with the PubSub pattern. That said, you may like that the author is normalizing it with the PubSub pattern. In which case you may also want to rename "notify" to "publish". The author also assumes all "observers" are just callback functions while, in the spec, an "observer" is an object which implements an interface that bears an "update" method that gets called by "notify" whenever the Subject's state changes.

DEVIATIONS I RECOMMEND

Rename the Observer's interface method from "update" to "call". This way, you can pass both objects and functions in as an Observer since both should have a call method (this is the best way to normalize objects & functions in any situation).

Instead of using an Array to encapsulate the observers, use a Set; it is just easier to call .add & .delete (or .has to avoid adding multiple of the same observer).

Add a 2nd argument to the "attach" method called "notify". This is a boolean flag an observer can pass in to signal that it wants to be immediately invoked upon attachment in order to get the current state of the Subject and perform it's ritual. That is, in the "attach" method, you'll have a line such as if (notify) observer.call(observer, state);.

Take an optional parameter in the constructor of Subject that is a "key" so as to allow Subject to be reusable. This way a subclass can extend Subject and configure which datum of it's encapsulation represents the "state" to be concerned with. This not only allows you to not be married to a key called "state" but you can then even setup a get/set pair that automatically calls notify whenever this[key] gets set.

Once you make these changes then you'll have a higher ability to create different types of Subjects that behave in different ways if you'd like.

Hope that's useful for you.

Collapse
 
brdnicolas profile image
Nicolas B.

Thank you for your very detailed comment! I hope my next articles will be better :)

Collapse
 
raulferreirasilva profile image
Raul Ferreira

I had no knowledge on the subject, I found it very interesting and I want to apply it, I really liked the examples, they allowed me to have a better understanding and learn ways to do some, as I am new, having contact with codes is always a learning experience, thank you for sharing your knowledge 🦤.

Collapse
 
brdnicolas profile image
Nicolas B.

Thank's you Raul! I appreciate it! <3

Collapse
 
coolcucumbercat profile image
coolCucumber-cat

You should use a Set instead of an array to store the dependencies. It can only contain unique items and you can use the delete method instead of filtering it out. You can also modify it to create signals, which are basically observables, but they keep track of the value. When you create it, you pass in an initial value, and every time you notify it, it makes that the new value. Often instead of using a method (like notify), you use a setter for value, so you would do ˋsignal.value = 123ˋ instead of ˋsignal.notify(123)ˋ. Then you can use a getter to get the current value, like ˋsignal.valueˋ. I like this article because before this I never realised that signals are just advanced observables

Collapse
 
brdnicolas profile image
Nicolas B.

Thank's a lot for your feedback! My future articles will be better :)

Collapse
 
dsaga profile image
Dusan Petkovic

Maybe for the first example it makes more sense to make the class name something like Observable to indicate its actually the subject thats being observed.

Thanks!

Collapse
 
brdnicolas profile image
Nicolas B.

Really nice feedback, thanks! I change it.

Collapse
 
jonrandy profile image
Jon Randy 🎖️

"Ovserver"?