Module Design Pattern trong JavaScript

Module Design Pattern là một mẫu thiết kế (design pattern) phổ biến trong JavaScript, giúp tổ chức mã theo từng module riêng biệt. Mục tiêu chính của mẫu này là tạo ra một không gian riêng biệt cho mỗi module, giúp tránh xung đột giữa các biến và hàm (encapsulation), đồng thời cung cấp khả năng chia sẻ và tái sử dụng mã tốt hơn. Đây là cách để tạo ra các thành phần (components) dễ bảo trì, dễ mở rộng và có cấu trúc rõ ràng.

Tại sao cần sử dụng Module Design Pattern?

  1. Tránh xung đột biến toàn cục: Trong JavaScript, các biến và hàm toàn cục (global) có thể dễ dàng bị ghi đè bởi các phần khác của mã, dẫn đến xung đột và lỗi. Module pattern giúp tránh vấn đề này bằng cách tạo ra các không gian tên (namespace) riêng cho từng phần của mã.
  2. Tạo cấu trúc rõ ràng: Khi ứng dụng phát triển lớn hơn, việc phân chia mã thành các module riêng biệt giúp dễ quản lý và duy trì mã nguồn.
  3. Encapsulation (Bao đóng): Module pattern cho phép giữ một số biến và hàm trong phạm vi nội bộ (private), ngăn không cho chúng bị truy cập từ bên ngoài, giúp kiểm soát được tính toàn vẹn của dữ liệu.
  4. Tái sử dụng: Mỗi module có thể được tái sử dụng ở nhiều nơi trong ứng dụng mà không lo xung đột hay gây ảnh hưởng đến các phần khác.

Cách triển khai Module Design Pattern

IIFE (Immediately Invoked Function Expression)

Một cách phổ biến để triển khai module pattern trong JavaScript là sử dụng IIFE (hàm được gọi ngay lập tức). Điều này giúp tạo ra một không gian biệt lập cho các biến và hàm trong module, tránh việc chúng “rò rỉ” ra phạm vi toàn cục.

Ví dụ:

const myModule = (function() {
    // Biến và hàm private
    let privateVar = 'Tôi là một biến private';
    
    function privateMethod() {
        console.log('Tôi là một hàm private');
    }

    // Biến và hàm public (được return ra ngoài)
    return {
        publicVar: 'Tôi là một biến public',
        
        publicMethod: function() {
            console.log('Tôi là một hàm public');
            privateMethod();
        }
    };
})();

// Sử dụng module
console.log(myModule.publicVar); // Tôi là một biến public
myModule.publicMethod(); // Tôi là một hàm public
// Không thể truy cập vào biến hoặc hàm private
console.log(myModule.privateVar); // undefined
myModule.privateMethod(); // Error: myModule.privateMethod is not a function

Trong ví dụ trên:

  • privateVarprivateMethod là các phần tử “private”, chỉ có thể truy cập bên trong module.
  • publicVarpublicMethod là các phần tử “public”, có thể truy cập từ bên ngoài module.
  • Hàm privateMethod chỉ có thể được gọi thông qua publicMethod, tạo ra sự bao đóng (encapsulation).
Revealing Module Pattern

Revealing Module Pattern là một biến thể của module pattern, trong đó chúng ta định nghĩa tất cả các biến và hàm ở bên trong module, sau đó chỉ định rõ những gì sẽ được “lộ” (revealed) ra bên ngoài, tức là những thành phần nào sẽ trở thành public.

Ví dụ:

const myRevealingModule = (function() {
    let privateVar = 'Tôi là một biến private';
    
    function privateMethod() {
        console.log('Tôi là một hàm private');
    }
    
    function publicMethod() {
        console.log('Tôi là một hàm public');
        privateMethod();
    }
    
    function publicVarMethod() {
        return privateVar;
    }

    // Chỉ tiết lộ những gì muốn public
    return {
        publicMethod: publicMethod,
        publicVarMethod: publicVarMethod
    };
})();

// Sử dụng module
myRevealingModule.publicMethod(); // Tôi là một hàm public
console.log(myRevealingModule.publicVarMethod()); // Tôi là một biến private

Revealing Module Pattern giúp mã nguồn dễ hiểu hơn vì tất cả các phương thức và thuộc tính đều được định nghĩa trước khi chúng được “tiết lộ” ra ngoài. Điều này giúp dễ dàng quản lý các phần private và public.

Module Pattern với ES6

Với sự xuất hiện của ECMAScript 6 (ES6), JavaScript đã hỗ trợ mô-đun hóa natively bằng cách sử dụng từ khóa importexport. Điều này cung cấp một cách chuẩn hóa hơn để tổ chức mã thành các module mà không cần sử dụng IIFE.

Ví dụ:

// module.js
export const publicVar = 'Tôi là một biến public';

export function publicMethod() {
    console.log('Tôi là một hàm public');
}

// main.js
import { publicVar, publicMethod } from './module.js';

console.log(publicVar); // Tôi là một biến public
publicMethod(); // Tôi là một hàm public

Trong ES6:

  • export cho phép chúng ta “lộ” ra các biến, hàm, hoặc lớp từ một module.
  • import cho phép chúng ta lấy các phần tử đã được export từ module khác để sử dụng.

Kết luận

Module Design Pattern là một mẫu thiết kế mạnh mẽ và hữu ích trong JavaScript, giúp phân chia mã thành các phần độc lập, dễ bảo trì và mở rộng. Với sự xuất hiện của ES6, module pattern trở nên rõ ràng và chuẩn hóa hơn, giúp lập trình viên quản lý các thành phần của ứng dụng một cách dễ dàng và có cấu trúc hơn.