Back to blog

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

oopencapsulationinformation-hidingaccess-modifierstypescriptpythonjava
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

  1. Bundling (Gói gọn): Giữ dữ liệu và hành vi liên quan cùng một nơi
  2. 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ữ

ModifierTypeScriptPythonJavaPhạm Vi
Publicpublic (mặc định)Không prefixpublicMọi nơi
Protectedprotected_prefixprotectedClass + subclass
Privateprivate__prefixprivateChỉ 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  # AttributeError

Getters và Setters với Validation

Tại Sao Cần Getters/Setters?

  1. Validation: Đảm bảo tính toàn vẹn dữ liệu
  2. Computed Properties: Tính toán giá trị on-the-fly
  3. Lazy Loading: Trì hoãn thao tác tốn kém
  4. 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 >= 18

Immutable 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

  1. Thread Safety: Không cần đồng bộ hóa cho concurrent access
  2. Predictability: Trạng thái không bao giờ thay đổi bất ngờ
  3. Hashable: Có thể dùng làm dictionary keys
  4. 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]"  # FrozenInstanceError

Ví 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());  // ******7890

Cá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ý logic

Tổng Kết

Khái Niệm Chính

Khái NiệmĐịnh NghĩaLợi Ích
EncapsulationGói gọn data và methodsTổ chức code
Information HidingHạn chế truy cập nội bộBảo vệ state
Access Modifierspublic, private, protectedKiểm soát visibility
Getters/SettersTruy cập có kiểm soát với validationToàn vẹn dữ liệu
ImmutabilityObjects không thay đổiThread 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

  1. Inheritance và Composition: Chiến lược code reuse
  2. Polymorphism và Interfaces: Design patterns linh hoạt
  3. 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.