Observers and Iterators

To understand where Observables come from we need to look at their foundations: the Observer and Iterator software patterns. In this section we’ll take a quick look at them, and then we’ll see how Observables combine concepts of both in a simple but powerful way.

The Observer Pattern

For a software developer, it’s hard to hear about Observables and not think of the venerable Observer pattern. In it we have an object called Producer that keeps an internal list of Listeners subscribed to it. Listeners are notified—by calling their update method—whenever the state of the Producer changes. (In most explanations of the Observer pattern, this entity is called Subject, but to avoid confusion with RxJS’s own Subject type, we call it Producer.)

It’s easy to implement a rudimentary version of the pattern in a few lines:

 class​ Producer {
 constructor​() {
 this​.listeners = [];
  }
 
  add(listener) {
 this​.listeners.push(listener);
  }
 
  remove(listener) {
 const​ index = ​this​.listeners.indexOf(listener);
 this​.listeners.splice(index, 1);
  }
 
  notify(message) {
 this​.listeners.forEach(listener => {
  listener.update(message);
  });
  }
 }

The Producer object keeps a list of Listeners in the instance’s listeners array that will all be updated whenever the Producer calls its notify method. In the following code we create two objects that listen to notifier, an instance of Producer:

 // Any object with an 'update' method would work.
 const​ listener1 = {
  update: message => {
  console.log(​"Listener 1 received:"​, message);
  }
 };
 
 const​ listener2 = {
  update: message => {
  console.log(​"Listener 2 received:"​, message);
  }
 };
 
 const​ notifier = ​new​ Producer();
 notifier.add(listener1);
 notifier.add(listener2);
 notifier.notify(​"Hello there!"​);

When we run the program

<= Listener 1 received: Hello there!
 Listener 2 received: Hello there!

listener1 and listener2 are notified whenever the Producer notifier updates its internal state, without us having to check for it.

Our implementation is simple, but it illustrates how the Observer pattern allows decoupling between the events and the listener objects that react to them.

The Iterator Pattern

The other piece in the Observable puzzle comes from the Iterator pattern. An Iterator is an object that provides a consumer with an easy way to traverse its contents, hiding the implementation from the consumer.

The Iterator interface is simple. It requires only two methods: next() to get the next item in the sequence, and hasNext() to check if there are items left in the sequence.

Here’s how we’d write an iterator that operates on an array of numbers and yields only elements that are multiples of the divisor parameter:

 class​ MultipleIterator {
 constructor​(arr, divisor = 1) {
 this​.cursor = 0;
 this​.array = arr;
 this​.divisor = divisor;
  }
 
  next() {
 while​ (​this​.cursor < ​this​.array.length) {
 const​ value = ​this​.array[​this​.cursor++];
 if​ (value % ​this​.divisor === 0) {
 return​ value;
  }
  }
  }
 
  hasNext() {
 let​ cur = ​this​.cursor;
 while​ (cur < ​this​.array.length) {
 if​ (​this​.array[cur++] % ​this​.divisor === 0) {
 return​ ​true​;
  }
  }
 return​ ​false​;
  }
 }

We can use this iterator like this:

 const​ consumer = ​new​ iterateOnMultiples(
  [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
  3
 );
 
 console.log(consumer.next(), consumer.hasNext()); ​// 3 true
 console.log(consumer.next(), consumer.hasNext()); ​// 6 true
 console.log(consumer.next(), consumer.hasNext()); ​// 9 false

Iterators offer a great way to encapsulate traversing logic for any kind of data structure. As we saw in the preceding example, iterators get interesting when made generic to handle different types of data, or when they can be configured in runtime, like we did in our example with the divisor parameter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.5.12