Encapsulation và Information Hiding: Bảo Vệ Trạng Thái Đối Tượng

Giới Thiệu
Bài viết này đi sâu vào Encapsulation (Đóng gói) - trụ cột đầu tiên và quan trọng nhất của Lập trình Hướng đối tượng. Encapsulation là nền tảng mà tất cả các nguyên tắc OOP khác được xây dựng lên.
Bạn Sẽ Học Được
✅ Hiểu sâu về encapsulation và information hiding
✅ Sử dụng access modifiers (public, private, protected) đúng cách
✅ Triển khai getters và setters với validation phù hợp
✅ Tạo immutable objects cho thread safety
✅ Nhận biết và sửa các lỗi encapsulation phổ biến
Encapsulation Là Gì?
Encapsulation là việc gói gọn dữ liệu (attributes) và các phương thức thao tác trên dữ liệu đó trong một đơn vị (class), đồng thời hạn chế truy cập trực tiếp vào các thành phần bên trong.
Hai Khía Cạnh Chính
- Bundling (Gói gọn): Giữ dữ liệu và hành vi liên quan cùng một nơi
- Information Hiding (Ẩn thông tin): Kiểm soát truy cập vào chi tiết triển khai
Ví Dụ Viên Thuốc
Tưởng tượng một viên thuốc con nhộng. Các thành phần hoạt chất được bảo vệ bên trong vỏ. Bạn không cần biết thành phần hóa học chính xác—chỉ cần uống và nó hoạt động. Vỏ thuốc:
- Bảo vệ nội dung khỏi tác động bên ngoài
- Kiểm soát cách thuốc được giải phóng
- Ẩn sự phức tạp của công thức bên trong
Access Modifiers
So Sánh Giữa Các Ngôn Ngữ
| Modifier | TypeScript | Python | Java | Phạm Vi |
|---|---|---|---|---|
| Public | public (mặc định) | Không prefix | public | Mọi nơi |
| Protected | protected | _prefix | protected | Class + subclass |
| Private | private | __prefix | private | Chỉ trong class |
TypeScript
class Employee {
public name: string; // Truy cập mọi nơi
protected department: string; // Class và subclass
private salary: number; // Chỉ trong class này
public readonly id: string; // Không thể thay đổi sau khởi tạo
constructor(name: string, department: string, salary: number, id: string) {
this.name = name;
this.department = department;
this.salary = salary;
this.id = id;
}
public getAnnualSalary(): number {
return this.salary * 12;
}
protected calculateBonus(): number {
return this.salary * 0.1;
}
private formatCurrency(amount: number): string {
return `$${amount.toLocaleString()}`;
}
}
class Manager extends Employee {
getManagerInfo(): string {
// ✅ Truy cập được public và protected
return `${this.name} - ${this.department}`;
// ❌ Không truy cập được private
// this.salary; // Lỗi!
}
}Python (Theo Quy Ước)
class Employee:
def __init__(self, name: str, department: str, salary: float, employee_id: str):
self.name = name # Public
self._department = department # Protected (quy ước)
self.__salary = salary # Private (name mangling)
self._EMPLOYEE_ID = employee_id
def get_annual_salary(self) -> float:
return self.__salary * 12
def _calculate_bonus(self) -> float: # Protected
return self.__salary * 0.1
def __format_currency(self, amount: float) -> str: # Private
return f"${amount:,.2f}"
class Manager(Employee):
def get_manager_info(self) -> str:
# ✅ Truy cập public và protected
return f"{self.name} - {self._department}"
# ❌ Private bị name mangling
# self.__salary # AttributeErrorGetters và Setters với Validation
Tại Sao Cần Getters/Setters?
- Validation: Đảm bảo tính toàn vẹn dữ liệu
- Computed Properties: Tính toán giá trị on-the-fly
- Lazy Loading: Trì hoãn thao tác tốn kém
- Tính linh hoạt: Thay đổi implementation mà không ảnh hưởng interface
Ví Dụ: User với Validation
TypeScript:
class User {
private _email: string;
private _age: number;
private _password: string;
constructor(email: string, age: number, password: string) {
this.email = email; // Sử dụng setter để validate
this.age = age;
this.password = password;
}
get email(): string {
return this._email;
}
set email(value: string) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(value)) {
throw new Error(`Email không hợp lệ: ${value}`);
}
this._email = value.toLowerCase().trim();
}
get age(): number {
return this._age;
}
set age(value: number) {
if (value < 0 || value > 150) {
throw new Error(`Tuổi không hợp lệ: ${value}`);
}
this._age = Math.floor(value);
}
// Password chỉ cho phép ghi, không đọc
set password(value: string) {
if (value.length < 8) {
throw new Error("Mật khẩu phải có ít nhất 8 ký tự");
}
this._password = this.hashPassword(value);
}
checkPassword(attempt: string): boolean {
return this.hashPassword(attempt) === this._password;
}
private hashPassword(password: string): string {
return Buffer.from(password).toString("base64");
}
// Computed property
get isAdult(): boolean {
return this._age >= 18;
}
}
// Sử dụng
const user = new User("[email protected]", 25, "SecurePass123");
console.log(user.email); // [email protected] (đã normalize)
console.log(user.isAdult); // true
user.age = 30; // ✅ Hợp lệ
// user.age = -5; // ❌ Lỗi: Tuổi không hợp lệPython:
import re
import hashlib
class User:
def __init__(self, email: str, age: int, password: str):
self.email = email
self.age = age
self.password = password
@property
def email(self) -> str:
return self._email
@email.setter
def email(self, value: str) -> None:
if not re.match(r'^[^\s@]+@[^\s@]+\.[^\s@]+$', value):
raise ValueError(f"Email không hợp lệ: {value}")
self._email = value.lower().strip()
@property
def age(self) -> int:
return self._age
@age.setter
def age(self, value: int) -> None:
if not 0 <= value <= 150:
raise ValueError(f"Tuổi không hợp lệ: {value}")
self._age = int(value)
@property
def password(self) -> None:
raise AttributeError("Password chỉ cho phép ghi")
@password.setter
def password(self, value: str) -> None:
if len(value) < 8:
raise ValueError("Mật khẩu phải có ít nhất 8 ký tự")
self._password_hash = hashlib.sha256(value.encode()).hexdigest()
def check_password(self, attempt: str) -> bool:
return hashlib.sha256(attempt.encode()).hexdigest() == self._password_hash
@property
def is_adult(self) -> bool:
return self._age >= 18Immutable Objects (Đối Tượng Bất Biến)
Immutable objects không thể thay đổi sau khi tạo. Điều này cung cấp thread safety và tính dự đoán được.
Lợi Ích của Immutability
- Thread Safety: Không cần đồng bộ hóa cho concurrent access
- Predictability: Trạng thái không bao giờ thay đổi bất ngờ
- Hashable: Có thể dùng làm dictionary keys
- Dễ Debug: Trạng thái tại bất kỳ thời điểm nào đều rõ ràng
Ví Dụ Immutable Class
TypeScript:
class ImmutableUser {
readonly id: string;
readonly email: string;
readonly name: string;
readonly createdAt: Date;
constructor(id: string, email: string, name: string) {
this.id = id;
this.email = email;
this.name = name;
this.createdAt = new Date();
Object.freeze(this);
}
// "Setter" trả về instance mới thay vì thay đổi
withEmail(newEmail: string): ImmutableUser {
return new ImmutableUser(this.id, newEmail, this.name);
}
withName(newName: string): ImmutableUser {
return new ImmutableUser(this.id, this.email, newName);
}
}
// Sử dụng
const user1 = new ImmutableUser("1", "[email protected]", "John");
const user2 = user1.withEmail("[email protected]");
console.log(user1.email); // [email protected] (không thay đổi)
console.log(user2.email); // [email protected] (instance mới)Python:
from dataclasses import dataclass
from datetime import datetime
@dataclass(frozen=True) # frozen=True tạo immutable class
class ImmutableUser:
id: str
email: str
name: str
created_at: datetime = None
def __post_init__(self):
if self.created_at is None:
object.__setattr__(self, 'created_at', datetime.now())
def with_email(self, new_email: str) -> "ImmutableUser":
return ImmutableUser(
id=self.id,
email=new_email,
name=self.name,
created_at=self.created_at
)
# Sử dụng
user1 = ImmutableUser("1", "[email protected]", "John")
user2 = user1.with_email("[email protected]")
# user1.email = "[email protected]" # FrozenInstanceErrorVí Dụ Thực Tế: Bank Account
interface Transaction {
id: string;
type: "deposit" | "withdrawal" | "transfer";
amount: number;
timestamp: Date;
balanceAfter: number;
}
class BankAccount {
private balance: number;
private readonly transactions: Transaction[] = [];
private isLocked: boolean = false;
public readonly accountNumber: string;
public readonly accountHolder: string;
private static readonly DAILY_LIMIT = 10000;
constructor(accountNumber: string, accountHolder: string, initialDeposit: number = 0) {
this.validateAccountNumber(accountNumber);
this.accountNumber = accountNumber;
this.accountHolder = accountHolder;
this.balance = 0;
if (initialDeposit > 0) {
this.deposit(initialDeposit, "Nạp tiền ban đầu");
}
}
private validateAccountNumber(accountNumber: string): void {
if (!/^\d{10}$/.test(accountNumber)) {
throw new Error("Số tài khoản phải có đúng 10 chữ số");
}
}
private checkAccountStatus(): void {
if (this.isLocked) {
throw new Error("Tài khoản bị khóa. Vui lòng liên hệ CSKH.");
}
}
getBalance(): number {
this.checkAccountStatus();
return this.balance;
}
getMaskedAccountNumber(): string {
return "******" + this.accountNumber.slice(-4);
}
deposit(amount: number, description?: string): Transaction {
this.checkAccountStatus();
if (amount <= 0) {
throw new Error("Số tiền nạp phải dương");
}
this.balance += amount;
const transaction = this.recordTransaction("deposit", amount, description);
return transaction;
}
withdraw(amount: number, description?: string): Transaction {
this.checkAccountStatus();
if (amount <= 0) {
throw new Error("Số tiền rút phải dương");
}
if (amount > this.balance) {
throw new Error(`Số dư không đủ. Khả dụng: ${this.balance}`);
}
this.balance -= amount;
return this.recordTransaction("withdrawal", amount, description);
}
private recordTransaction(
type: Transaction["type"],
amount: number,
description?: string
): Transaction {
const transaction: Transaction = {
id: `TXN-${Date.now()}`,
type,
amount,
timestamp: new Date(),
balanceAfter: this.balance,
};
this.transactions.push(transaction);
return transaction;
}
// Trả về bản sao, không phải reference gốc
getTransactionHistory(): readonly Transaction[] {
this.checkAccountStatus();
return [...this.transactions];
}
}
// Sử dụng
const account = new BankAccount("1234567890", "Nguyễn Văn A", 1000);
account.deposit(500, "Lương tháng 2");
account.withdraw(200, "Rút ATM");
console.log(account.getBalance()); // 1300
console.log(account.getMaskedAccountNumber()); // ******7890Các Lỗi Encapsulation Phổ Biến
Lỗi 1: Expose Internal Collection
// ❌ SAI: Trả về reference của collection nội bộ
class ShoppingCart {
private items: string[] = [];
getItems(): string[] {
return this.items; // Trả về reference thực!
}
}
const cart = new ShoppingCart();
cart.getItems().push("hacked item"); // Thay đổi state nội bộ!
// ✅ ĐÚNG: Trả về defensive copy
class ShoppingCart {
private items: string[] = [];
getItems(): readonly string[] {
return [...this.items]; // Trả về bản sao
}
}Lỗi 2: Anemic Domain Model
// ❌ SAI: Object chỉ chứa data, logic ở nơi khác
class Order {
public status: string;
public items: OrderItem[];
}
class OrderService {
ship(order: Order): void {
if (order.status !== "paid") throw new Error("...");
order.status = "shipped";
}
}
// ✅ ĐÚNG: Behavior đi cùng data
class Order {
private _status: OrderStatus = "pending";
private readonly _items: OrderItem[] = [];
ship(): void {
if (this._status !== "paid") {
throw new Error("Không thể giao đơn chưa thanh toán");
}
this._status = "shipped";
}
}Lỗi 3: Leak Mutable Object
// ❌ SAI: Expose mutable Date
class Event {
private startDate: Date;
getStartDate(): Date {
return this.startDate; // Cùng Date object
}
}
event.getStartDate().setFullYear(1900); // Thay đổi internal state!
// ✅ ĐÚNG: Return copy
class Event {
private startDate: Date;
getStartDate(): Date {
return new Date(this.startDate.getTime()); // Copy mới
}
}Best Practices
1. Mặc Định là Private
Làm mọi thứ private trước. Chỉ expose những gì thực sự cần thiết.
2. Validate Tất Cả Input
Không bao giờ tin dữ liệu đến từ bên ngoài.
3. Sử Dụng Immutability Khi Có Thể
Immutable objects tự nhiên được encapsulate tốt.
4. Tell, Don't Ask
Thay vì hỏi data và xử lý bên ngoài, nói cho object biết cần làm gì.
// ❌ SAI: Ask và xử lý bên ngoài
if (account.getBalance() >= amount) {
account.setBalance(account.getBalance() - amount);
}
// ✅ ĐÚNG: Tell object làm gì
account.withdraw(amount); // Object tự xử lý logicTổng Kết
Khái Niệm Chính
| Khái Niệm | Định Nghĩa | Lợi Ích |
|---|---|---|
| Encapsulation | Gói gọn data và methods | Tổ chức code |
| Information Hiding | Hạn chế truy cập nội bộ | Bảo vệ state |
| Access Modifiers | public, private, protected | Kiểm soát visibility |
| Getters/Setters | Truy cập có kiểm soát với validation | Toàn vẹn dữ liệu |
| Immutability | Objects không thay đổi | Thread safety |
Checklist Encapsulation
✅ Tất cả fields là private mặc định
✅ Public API tối thiểu và có chủ đích
✅ Validation trong setters
✅ Defensive copies cho mutable objects
✅ Không expose internal collections
✅ Behavior đi cùng data (không anemic model)
Tiếp Theo
- Inheritance và Composition: Chiến lược code reuse
- Polymorphism và Interfaces: Design patterns linh hoạt
- SOLID Principles: Clean OOP design
Bài Tập Thực Hành
Bài Tập 1: Temperature Converter
Tạo class Temperature với encapsulation:
- Lưu trữ nội bộ bằng Celsius (private)
- Getters cho Celsius, Fahrenheit, và Kelvin
- Validate không được dưới absolute zero (-273.15°C)
- Immutable (trả về instance mới khi convert)
Bài Tập 2: Password Manager Entry
Tạo class PasswordEntry:
- Lưu website, username, và encrypted password
- Không bao giờ expose raw password
- Validate URL format và username requirements
- Track last accessed và last modified dates
Bài Tập 3: Shopping Cart
Tạo class ShoppingCart với encapsulation:
- Lưu items privately với quantities
- Validate quantities (positive integers)
- Read-only access đến items (defensive copy)
- Tính total với encapsulation đúng
- Ngăn modification sau checkout
Có câu hỏi hoặc góp ý? Liên hệ qua [email protected] hoặc kết nối trên X.
Happy coding! 🚀
📬 Đăng ký nhận bản tin
Nhận những bài viết mới nhất qua email mỗi tuần. Không spam, hủy đăng ký bất cứ lúc nào.
Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất cứ lúc nào.
💬 Comments
Đăng nhập để bình luận
Chúng tôi sẽ không đăng bất cứ điều gì mà không có sự cho phép của bạn.