JavaScript design patterns #5. The Observer pattern with TypeScript

栏目: IT技术 · 发布时间: 5年前

内容简介:The Observer pattern is common among various native JavaScript features and many libraries. The above is just one of the reasons to look into it more. In this article, we learn the principles of the Observer pattern. We also add some TypeScript typings to

The Observer pattern is common among various native JavaScript features and many libraries. The above is just one of the reasons to look into it more. In this article, we learn the principles of the Observer pattern. We also add some TypeScript typings to it, to make it more bug-proof.

The core principle of the Observer pattern is defining a mechanism of subscription to notify various objects about events. The notified objects have to explicitly state that they are interested in the above events first. The above is a common situation in JavaScript. We sometimes have some libraries taking care of the above tasks. On the other hand, it might be beneficial to know how it works under the hood.

The subject holds a list of  observers and calls every one of them in case it wants to communicate something.

class Subject {
  observers = [];
}

There are a few things to consider when designing a list of observers . Let’s assume that we want it to have no duplicates. The second thing that we want to provide is a straightforward way to  unsubscribe from the list of observers . A better candidate than an array to implement the above features is a  Set .

class Subject {
  observers = new Set();
}

The Set object lets you store unique values of any type, whether primitive values or object references

The observer with the update function

There are a few approaches that we can take. The first of them includes creating an Observer class.

class Observer {
  update(message) {
    console.log(message);
  }
}

Our Observer class contains the  update method. Let’s now expect every Observer to implement it. With that knowledge, we can continue writing the  Subject class.

Since now we expect every Observer to implement the  update method, we call it when we want to notify our observers.

class Subject {
  observers = new Set();
 
  subscribe(observer) {
    this.observers.add(observer);
  }
 
  notify(message) {
    this.observers.forEach((observer) => {
      observer.update(message);
    })
  }
}
const subject = new Subject();
 
subject.subscribe(new Observer());
subject.subscribe(new Observer());
 
subject.notify('Hello world!');
Hello world!  Hello world!

Thanks to using a Set, we don’t have to worry about duplicates when subscribing. Therefore, they will be omitted.

An important note is that a Set looks at object references when checking for duplicates. Even though we added two identical observers, they are two different objects.

new Observer() === new Observer() // false
const subject = new Subject();
 
const observer = new Observer();
subject.subscribe(observer);
subject.subscribe(observer);
 
subject.notify('Hello world!');
Hello world!

Since above we subscribe using the same observer twice, it is added only once.

The thing left to implement is the unsubscribe method.

unsubscribe(observer) {
  this.observers.delete(observer);
}

The same thing applies to the delete function of a Set. Therefore, our  unsubscribe function needs to be provided with the same object in order to delete it.

Unsubscribing to prevent the lapsed listener issue

Remember always to unsubscribe if you don’t need the observer to listen anymore. Forgetting to do so prevents the observer from being garbage-collected. This is an issue that we call the lapsed listener problem . A solution to the above problem might be using the  WeakSet instead of Set, but it doesn’t allow us to iterate its elements.

The observer as a function

Another popular approach would be to use functions as observers. This time we don’t need the Observer class.

class Subject {
  observers = new Set();
 
  subscribe(observer) {
    this.observers.add(observer);
  }
 
  unsubscribe(observer) {
    this.observers.delete(observer);
  }
 
  notify(message) {
    this.observers.forEach((observer) => {
      observer(message);
    })
  }
}
const subject = new Subject();
 
subject.subscribe((message) => {
  console.log(message);
});
 
subject.notify('Hello world!');
Hello world!

The above approach might ring a bell for you, and rightfully so. We can often see it when using native JavaScript features.

window.addEventListener('load', () => {
  console.log('Page loaded')
});

We can take advantage of the above functionality and use it with our Subject to add some default logic to our event observers.

class PhoneInputSubject {
  observers = new Set();
 
  subscribe(observer) {
    this.observers.add(observer);
  }
 
  unsubscribe(observer) {
    this.observers.delete(observer);
  }
 
  shouldObserversBeNotified(message) {
    return /[+]*[0-9]+[+. -]*$/.test(message);
  }
 
  notify(message) {
    if(this.shouldObserversBeNotified(message)) {
      this.observers.forEach((observer) => {
        observer(message);
      })
    }
  }
}
const subject = new PhoneInputSubject();
 
subject.subscribe((phoneNumber) => {
  console.log(`A new proper phone number: ${phoneNumber}`);
});
 
window.addEventListener('load', () => {
  const phoneInput = document.querySelector('#phoneInput');
  if (phoneInput) {
    phoneInput.addEventListener('change', (event) => {
      subject.notify(event.target.value)
    });
  }
});

Adding TypeScript to our Observer

Expecting the Observer to have the  update method or to be a function is a rather bold assumption. It is a very fitting place to introduce TypeScript. First, let’s define the basics of our Observer :

interface Observer {
  update: (message: any) => void;
}

Above, we demand that every observer implements the update method.  We don’t enforce the message type here, so we leave it as  any .

We use the above interface in our Subject class.

class Subject {
  private observers = new Set<Observer>();
 
  subscribe(observer: Observer) {
    this.observers.add(observer);
  }
 
  unsubscribe(observer: Observer) {
    this.observers.delete(observer);
  }
 
  notify(message: string) {
    this.observers.forEach((observer) => {
      observer.update(message);
    })
  }
}

The essential thing above is that the Subject accepts any Observer as long as it has the  update method.

class MyObserver implements Observer {
  update(message: string) {
    console.log(message);
  }
}
const subject = new Subject();
 
const observer = new MyObserver();
subject.subscribe(observer);
 
subject.notify('Hello world!');

The above approach makes our Subject highly reusable and generic while making sure that the  update function is there.

class MyObserver implements Observer {
  hello() {
    console.log('Hello world!')
  }
}
Class ‘MyObserver’ incorrectly implements interface ‘Observer’.  Property ‘update’ is missing in type ‘MyObserver’ but required in type ‘Observer’.

Summary

In this article, we’ve gone through a popular and useful design pattern. Implementing it from the ground up gave us a bit of an insight into how this mechanism could work in some of the native JavaScript features. Aside from using JavaScript, we’ve also implemented TypeScript. By doing so, we’ve taken a precaution to make sure that we’ve built our observers properly. Learning all of the above surely gives us an additional tool under our programming belt.


以上所述就是小编给大家介绍的《JavaScript design patterns #5. The Observer pattern with TypeScript》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

数据挖掘导论

数据挖掘导论

Pang-Ning Tan、Michael Steinbach、Vipin Kumar / 范明、范宏建 / 人民邮电出版社 / 2010-12-10 / 69.00元

本书全面介绍了数据挖掘,涵盖了五个主题:数据、分类、关联分析、聚类和异常检测。除异常检测外,每个主题都有两章。前一章涵盖基本概念、代表性算法和评估技术,而后一章讨论高级概念和算法。这样读者在透彻地理解数据挖掘的基础的同时,还能够了解更多重要的高级主题。 本书是明尼苏达大学和密歇根州立大学数据挖掘课程的教材,由于独具特色,正式出版之前就已经被斯坦福大学、得克萨斯大学奥斯汀分校等众多名校采用。 ......一起来看看 《数据挖掘导论》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器