Singleton pattern trong Javascript

Để tiếp tục series Design pattern trong Javascript thì hôm nay mình sẽ giới thiệu về một design pattern rất nổi tiếng - Singleton pattern.

I. Giới thiệu chung

Singleton pattern được biết đến như là một mẫu thiết kế để chỉ có 1 object của một class được sinh ra trên toàn hệ thống. Ở những ngôn ngữ lập trình cổ điển, Singleton có thể được implement bằng cách khai báo trong class một method dùng để khởi tạo một object mới với điều kiện là chưa từng có object nào thuộc class đó được tạo ra trước đó. Trong trường hợp đã có object được khởi tạo trước đó, method đó sẽ trả về một references đến object đó.

Singleton hữu dụng khi sử dụng để quản lý các nguồn tài nguyên bị giới hạn hoặc theo dõi toàn bộ trạng thái của hệ thống. Một ví dụ cụ thể đó là database connection pool, nó sẽ quản lý toàn bộ các database connection của toàn bộ hệ thống và thông báo khi có bất cứ connection nào mất kết nối. Hoặc một ví dụ khác đó là phần quản lý âm lượng, không thể mỗi lần điều chỉnh âm lượng bạn lại tạo ra một instance mới của class Volumn được đúng không nào ? Đó là những trường hợp mà chúng ta nên sử dụng Singleton.

Yêu cầu với một singleton là:

Singleton phải có tính bất biến với đoạn code sử dụng nó, và phải không có nguy cơ nào xảy ra khi chúng ta tạo ra một instance mới từ class đó.

II. Singleton trong Javascript

1. Cách cổ điển

Trước đây việc implement Singleton trong Javascript khá khó nhằn, chúng ta phải sử dụng đến closures và immediately invoked function expression. Đây là một ví dụ cho một Singleton (khá đơn giản) được viết bằng cách này:

var UserStore = (function(){
  var _data = [];

  function add(item){
    _data.push(item);
  }

  function get(id){
    return _data.find((d) => {
      return d.id === id;
    });
  }

  return {
    add: add,
    get: get
  };
}());

Ở đây, UserStore sẽ là kết quả trả về của immediately invoked function trên - một object có 2 function có thể truy cập được, nhưng không cho phép truy cập trực tiếp vào các data của nó.

Nói chung, đoạn code ở phía trên khá loằng ngoằng và khó hiểu, cùng với đó là nó không có sự bất biến mà chúng ta cần khi implement một singleton. Chúng ta có thể thay đổi các hàm được trả về, hoặc thậm chí redefine luôn cả UserStore.

2. Sử dụng ES6

Với sự cập nhật của ES6, chúng ta có thể viết Singleton một các ngắn gọn hơn và còn đúng hơn với yêu cầu được đề ra.

Hãy bắt đầu với một implement thật đơn giản nào:

const _data = [];

const UserStore = {
  add: item => _data.push(item),
  get: id => _data.find(d => d.id === id)
}

Object.freeze(UserStore);
export default UserStore;

Như bạn có thể thấy, đoạn code phía trên đã trở nên dễ đọc hơn rất nhiều. Nhưng điểm sáng của đoạn code này đó là UserStore ở đây có tính bất biến: với việc sử dụng const thì ở những đoạn code sau này, chúng ta không thể redefine lại UserStore được nữa, và cùng với đó là việc sử dụng phương thức Object.freeze(UserStore) thì các phương thức nằm trong UserStore sẽ được giữ nguyên.

Ở trên đây, chúng ta đã biến UserStore thành một object literal. Phần lớn trường hợp thì việc sử dụng một object literal là xúc tích và dễ đọc nhất. Nhưng có những trường hợp chúng ta cần sử dụng sức mạnh của các traditional class.

Đây là cách chúng ta implement Singleton trong ES6 theo kiểu Class:

class UserStore {
  constructor(){
    this._data = [];
  }

  add(item){
    this._data.push(item);
  }

  get(id){
    return this._data.find(d => d.id === id);
  }
}

const instance = new UserStore();
Object.freeze(instance);

export default instance;

Cách này có hơi dài dòng hơn một chút so với việc sử dụng object literal, và ví dụ của chúng ta quá đơn giản để chúng ta có thể thấy được tác dụng của việc sử dụng một class. Nhưng có một tác dụng chúng ta có thể thấy ngay đó là nếu như đây là front-end code, và backend của các bạn được viết bằng Java hay C# thì chúng ta có thể sử dụng lại các design pattern (không chỉ Singleton) được viết ở backend để share với front-end nhằm làm tăng hiệu suất làm việc cho team của bạn.

III. Phần kết

Trên đây chỉ là những ví dụ rất cơ bản về Singleton trong Javascript. Ứng dụng của Singleton là rất nhiều trong các ứng dụng thực tế, vậy nên các bạn hãy trải nghiệm để hiểu rõ sức mạnh của nó.

Bài viết này được tham khảo từ một số nguồn:

  1. https://www.sitepoint.com/javascript-design-patterns-singleton/
  2. https://addyosmani.com/resources/essentialjsdesignpatterns/book/#modulepatternjavascript

All Rights Reserved