JavaScript -封装 (Encapsulation)

Xin chào các bạn lập trình viên đang trên đà phát triển! Hôm nay, chúng ta sẽ bắt đầu một chuyến hành trình thú vị vào thế giới của việc封装 trong JavaScript. Đừng lo lắng nếu bạn mới bắt đầu học lập trình - tôi sẽ là người hướng dẫn thân thiện của bạn, và chúng ta sẽ cùng khám phá khái niệm này từng bước một. Vậy, hãy lấy cặp sách ảo của bạn và bắt đầu nhé!

JavaScript - Encapsulation

什么是封装 (What is Encapsulation?)

Hãy tưởng tượng bạn có một két bảo mật. Bạn không muốn bất kỳ ai mở nó và lấy những viên đá quý của bạn, phải không? Đó chính là điều mà封装 làm trong lập trình. Nó giống như đặt mã của bạn trong một quả bóng bảo vệ, chỉ cho phép một số phần được truy cập từ bên ngoài.

Nói đơn giản hơn,封装 là việc gói gọn dữ liệu và các phương thức hoạt động trên dữ liệu đó trong một đơn vị hoặc đối tượng duy nhất. Đây là cách che giấu chi tiết nội bộ của cách một đối tượng hoạt động và chỉ tiết lộ những gì cần thiết.

Tại sao cần封装? (Why is encapsulation needed?)

Bạn có thể tự hỏi, "Tại sao chúng ta cần这个东西 phức tạp này?" Hãy để tôi kể cho bạn một câu chuyện nhỏ.

Ngày xưa, trong một vùng đất của mã mì Ý, có một lập trình viên tên là Bob. Mã của Bob mở cho bất kỳ ai thay đổi, và sớm thôi, các lập trình viên khác bắt đầu can thiệp vào. Chaos ensues! Không ai biết phần mã nào làm gì, và lỗi phát sinh như những con thỏ sinh sản.

Đây là nơi mà封装 đến để cứu nguy. Nó giúp chúng ta:

  1. Bảo vệ dữ liệu khỏi việc thay đổi ngẫu nhiên
  2. Che giấu chi tiết thực hiện phức tạp
  3. Làm cho mã của chúng ta trở nên có tổ chức hơn và dễ bảo trì hơn
  4. Giảm thiểu sự phụ thuộc giữa các phần khác nhau của mã chúng ta

Các cách khác nhau để đạt được封装 trong JavaScript (Different Ways to Achieve Encapsulation in JavaScript)

Trong JavaScript, chúng ta có một số tuyệt kỹ trong túi để đạt được封装. Hãy cùng khám phá chúng một lần một:

Đạt được封装 bằng cách sử dụngClosure (Achieving Encapsulation Using Function Closures)

Closure giống như những quả bóng ma thuật trong JavaScript mà nhớ lại môi trường mà chúng được tạo ra. Chúng ta có thể sử dụng chúng để tạo ra các biến và phương thức riêng. Hãy xem một ví dụ:

function createBankAccount(initialBalance) {
let balance = initialBalance;

return {
deposit: function(amount) {
balance += amount;
console.log(`Deposited ${amount}. New balance is ${balance}`);
},
withdraw: function(amount) {
if (amount <= balance) {
balance -= amount;
console.log(`Withdrawn ${amount}. New balance is ${balance}`);
} else {
console.log("Insufficient funds!");
}
},
getBalance: function() {
return balance;
}
};
}

const myAccount = createBankAccount(1000);
myAccount.deposit(500);  // Deposited 500. New balance is 1500
myAccount.withdraw(200); // Withdrawn 200. New balance is 1300
console.log(myAccount.getBalance()); // 1300
console.log(myAccount.balance); // undefined

Trong ví dụ này, balance là biến riêng của chúng ta. Nó không thể truy cập trực tiếp từ bên ngoài hàm createBankAccount. Chúng ta chỉ có thể tương tác với nó qua các phương thức mà chúng ta đã tiết lộ: deposit, withdraw, và getBalance. Đây là việc封装 trong hành động!

Đạt được封装 bằng cách sử dụng các lớp ES6 và các biến riêng (Achieving Encapsulation Using ES6 Classes and Private Variables)

Với sự ra đời của các lớp ES6, chúng ta có một cách quen thuộc hơn để tạo đối tượng và đạt được封装. Dưới đây là cách chúng ta có thể làm điều đó:

class BankAccount {
#balance;  // Private field

constructor(initialBalance) {
this.#balance = initialBalance;
}

deposit(amount) {
this.#balance += amount;
console.log(`Deposited ${amount}. New balance is ${this.#balance}`);
}

withdraw(amount) {
if (amount <= this.#balance) {
this.#balance -= amount;
console.log(`Withdrawn ${amount}. New balance is ${this.#balance}`);
} else {
console.log("Insufficient funds!");
}
}

getBalance() {
return this.#balance;
}
}

const myAccount = new BankAccount(1000);
myAccount.deposit(500);  // Deposited 500. New balance is 1500
myAccount.withdraw(200); // Withdrawn 200. New balance is 1300
console.log(myAccount.getBalance()); // 1300
console.log(myAccount.#balance); // SyntaxError: Private field '#balance' must be declared in an enclosing class

Trong ví dụ này, chúng ta sử dụng ký tự # để khai báo balance là một trường riêng. Điều này có nghĩa là nó chỉ có thể được truy cập trong lớp BankAccount, không phải từ bên ngoài.

Đạt được封装 bằng cách sử dụng các phương thức getter và setter (Achieving Encapsulation Using Getters and Setters)

Getters và setters là các phương thức đặc biệt cho phép chúng ta xác định cách một thuộc tính được truy cập hoặc thay đổi. Chúng giống như những người bảo vệ cho thuộc tính của đối tượng của chúng ta. Hãy xem chúng trong hành động:

class Circle {
constructor(radius) {
this._radius = radius;
}

get radius() {
return this._radius;
}

set radius(value) {
if (value <= 0) {
throw new Error("Radius must be positive");
}
this._radius = value;
}

get area() {
return Math.PI * this._radius ** 2;
}
}

const myCircle = new Circle(5);
console.log(myCircle.radius); // 5
console.log(myCircle.area);   // 78.53981633974483

myCircle.radius = 10;
console.log(myCircle.radius); // 10

myCircle.radius = -1; // Error: Radius must be positive

Trong ví dụ này, chúng ta sử dụng một getter để đọc radius và một setter để thay đổi nó. Setter bao gồm một kiểm tra để đảm bảo rằng bán kính luôn dương. Chúng ta cũng có một getter cho area mà tính toán diện tích trên-the-fly.

Lợi ích của封装 trong JavaScript (Benefits of Encapsulation in JavaScript)

Bây giờ chúng ta đã xem cách đạt được封装, hãy tóm tắt lợi ích của nó:

  1. Bảo vệ dữ liệu: Nó ngăn chặn việc truy cập không được phép vào nội bộ của đối tượng của chúng ta.
  2. Độ linh hoạt: Chúng ta có thể thay đổi việc thực hiện nội bộ mà không ảnh hưởng đến mã bên ngoài sử dụng đối tượng của chúng ta.
  3. Tính modularity: Nó giúp tạo ra các đơn vị mã tự-contained, làm cho mã của chúng ta trở nên modularity và dễ quản lý hơn.
  4. Gỡ lỗi: Bằng cách giới hạn nơi dữ liệu có thể được thay đổi, việc theo dõi lỗi trở nên dễ dàng hơn.
  5. Trừu tượng hóa: Nó cho phép chúng ta che giấu chi tiết thực hiện phức tạp và cung cấp một giao diện đơn giản cho việc sử dụng đối tượng của chúng ta.

Bảng phương thức (Methods Table)

Dưới đây là bảng tóm tắt các phương thức chúng ta đã thảo luận để đạt được封装:

Phương thức Mô tả Ví dụ
Function Closures Sử dụng closure để tạo biến riêng function createObject() { let privateVar = 0; return { getVar: () => privateVar }; }
ES6 Classes with Private Fields Sử dụng # để khai báo trường riêng trong lớp class MyClass { #privateField; constructor() { this.#privateField = 0; } }
Getters and Setters Sử dụng các phương thức đặc biệt để kiểm soát truy cập vào thuộc tính class MyClass { get prop() { return this._prop; } set prop(value) { this._prop = value; } }

Và thế là, các bạn! Chúng ta đã cùng nhau hành trình qua vùng đất của封装, từ khái niệm cơ bản đến các cách khác nhau để thực hiện nó trong JavaScript. Nhớ rằng,封装 giống như một người giữ bí mật giỏi - nó biết điều gì cần chia sẻ và điều gì cần giữ riêng. Khi bạn tiếp tục hành trình lập trình của mình, bạn sẽ thấy封装 là một người bạn đáng tin cậy trong việc tạo ra mã vững chắc và dễ bảo trì.

Hãy tiếp tục thực hành, 保持好奇心, và chúc bạn lập trình vui vẻ!

Credits: Image by storyset