Back to blog

Bốn Trụ Cột của OOP: Nền Tảng Thiết Kế Hướng Đối Tượng

oopencapsulationinheritancepolymorphismabstractiontypescriptpythonjava
Bốn Trụ Cột của OOP: Nền Tảng Thiết Kế Hướng Đối Tượng

Giới thiệu

Bốn trụ cột của Lập trình Hướng Đối tượng (OOP) là những khái niệm nền tảng mà mọi developer đều phải hiểu để viết code sạch, dễ bảo trì và mở rộng. Các nguyên tắc này—Encapsulation, Inheritance, Polymorphism và Abstraction—hoạt động cùng nhau giúp bạn mô hình hóa các vấn đề thực tế một cách hiệu quả và xây dựng hệ thống phần mềm vững chắc.

Những gì bạn sẽ học

✅ Hiểu từng trụ cột trong bốn trụ cột OOP
✅ Xem cách các trụ cột hoạt động cùng nhau
✅ Áp dụng các nguyên tắc với ví dụ thực tế
✅ Nhận biết các vi phạm và anti-patterns phổ biến
✅ Viết code sạch hơn, dễ bảo trì hơn


Tổng quan: Bốn Trụ Cột

Trụ CộtMô tảLợi ích chính
EncapsulationGói dữ liệu và methods, ẩn trạng thái nội bộBảo trì & Bảo mật
InheritanceTạo phân cấp classes, tái sử dụng code qua quan hệ "is-a"Tái sử dụng Code
PolymorphismMột interface, nhiều implementationsLinh hoạt
AbstractionẨn phức tạp, chỉ hiển thị các tính năng thiết yếuĐơn giản

Cách chúng hoạt động cùng nhau

┌─────────────────────────────────────────────────────────────┐
│                       ABSTRACTION                           │
│   (Những gì thế giới bên ngoài thấy - interface đơn giản)   │
├─────────────────────────────────────────────────────────────┤
│                      ENCAPSULATION                          │
│   (Cách chúng ta bảo vệ và tổ chức trạng thái nội bộ)       │
├─────────────────────────────────────────────────────────────┤
│  INHERITANCE          │           POLYMORPHISM              │
│  (Cấu trúc code       │           (Linh hoạt hành vi        │
│   và tái sử dụng)     │            tại runtime)             │
└─────────────────────────────────────────────────────────────┘

Trụ Cột 1: Encapsulation (Đóng gói)

Encapsulation là việc gói dữ liệu (attributes) và các methods hoạt động 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 một số thành phần của object.

Ý tưởng cốt lõi

Hãy nghĩ về encapsulation như bảng điều khiển của xe hơi. Bạn không cần biết động cơ hoạt động như thế nào—bạn chỉ sử dụng vô lăng, bàn đạp và các điều khiển. Sự phức tạp được đóng gói phía sau một interface đơn giản.

Ví dụ: Không có Encapsulation (Tệ)

// ❌ TỆ: Không có encapsulation - ai cũng có thể sửa balance trực tiếp
class BankAccount {
  balance: number = 0;
  accountNumber: string;
 
  constructor(accountNumber: string) {
    this.accountNumber = accountNumber;
  }
}
 
const account = new BankAccount("123456");
account.balance = -1000; // Trạng thái không hợp lệ! Không có validation
account.balance = 999999999; // Gian lận! Không có bảo vệ

Ví dụ: Có Encapsulation (Tốt)

// ✅ TỐT: Encapsulation đúng cách
class BankAccount {
  private balance: number = 0;
  private readonly accountNumber: string;
 
  constructor(accountNumber: string, initialBalance: number = 0) {
    this.accountNumber = accountNumber;
    if (initialBalance > 0) {
      this.balance = initialBalance;
    }
  }
 
  // Truy cập có kiểm soát thông qua methods
  public deposit(amount: number): void {
    if (amount <= 0) {
      throw new Error("Số tiền gửi phải dương");
    }
    this.balance += amount;
  }
 
  public withdraw(amount: number): void {
    if (amount <= 0) {
      throw new Error("Số tiền rút phải dương");
    }
    if (amount > this.balance) {
      throw new Error("Không đủ tiền");
    }
    this.balance -= amount;
  }
 
  // Truy cập chỉ đọc vào balance
  public getBalance(): number {
    return this.balance;
  }
}
 
// Sử dụng
const account = new BankAccount("123456789", 1000);
account.deposit(500);     // ✅ Thay đổi có kiểm soát
account.withdraw(200);    // ✅ Có validation
console.log(account.getBalance()); // 1300
// account.balance = -1000;  // ❌ Lỗi: Property 'balance' là private

Lợi ích của Encapsulation

  1. Toàn vẹn dữ liệu: Validation ngăn trạng thái không hợp lệ
  2. Dễ bảo trì: Thay đổi implementation nội bộ không ảnh hưởng code bên ngoài
  3. Bảo mật: Ẩn dữ liệu và operations nhạy cảm
  4. Dễ test: Ranh giới rõ ràng giúp testing dễ dàng hơn
  5. Giảm coupling: Code bên ngoài phụ thuộc vào interface, không phải implementation

Trụ Cột 2: Inheritance (Kế thừa)

Inheritance là cơ chế cho phép một class (child/subclass) kế thừa properties và methods từ class khác (parent/superclass). Nó đại diện cho quan hệ "is-a".

Ý tưởng cốt lõi

Inheritance mô hình hóa các phân cấp thực tế. Dog là Animal. Manager là Employee. SportsCar là Car. Class con kế thừa tất cả đặc điểm của class cha và có thể thêm hoặc sửa đổi hành vi.

Ví dụ: Kế thừa cơ bản

// Class cha
class Animal {
  protected name: string;
 
  constructor(name: string) {
    this.name = name;
  }
 
  speak(): void {
    console.log(`${this.name} phát ra âm thanh`);
  }
 
  move(): void {
    console.log(`${this.name} di chuyển`);
  }
}
 
// Class con - kế thừa từ Animal
class Dog extends Animal {
  private breed: string;
 
  constructor(name: string, breed: string) {
    super(name); // Gọi constructor của cha
    this.breed = breed;
  }
 
  // Override method của cha
  speak(): void {
    console.log(`${this.name} sủa: Gâu gâu!`);
  }
 
  // Method mới riêng cho Dog
  fetch(): void {
    console.log(`${this.name} nhặt bóng`);
  }
}
 
class Cat extends Animal {
  constructor(name: string) {
    super(name);
  }
 
  // Override method của cha
  speak(): void {
    console.log(`${this.name} kêu: Meo meo!`);
  }
 
  // Method mới riêng cho Cat
  scratch(): void {
    console.log(`${this.name} cào đồ nội thất`);
  }
}
 
// Sử dụng
const dog = new Dog("Buddy", "Golden Retriever");
dog.speak();   // Buddy sủa: Gâu gâu!
dog.move();    // Buddy di chuyển (kế thừa)
dog.fetch();   // Buddy nhặt bóng
 
const cat = new Cat("Miu");
cat.speak();   // Miu kêu: Meo meo!
cat.move();    // Miu di chuyển (kế thừa)
cat.scratch(); // Miu cào đồ nội thất

Khi nào sử dụng Inheritance

✅ Sử dụng inheritance khi:

  • Có quan hệ "is-a" rõ ràng
  • Subclasses thực sự chia sẻ behavior và state
  • Bạn muốn tận dụng polymorphism
  • Liskov Substitution Principle được tuân thủ

❌ Tránh inheritance khi:

  • Quan hệ thực sự là "has-a" (dùng composition)
  • Bạn chỉ muốn tái sử dụng một số code (xem xét composition hoặc mixins)
  • Phân cấp trở nên quá sâu (hơn 2-3 cấp)

Trụ Cột 3: Polymorphism (Đa hình)

Polymorphism (từ tiếng Hy Lạp, có nghĩa "nhiều hình thức") cho phép các objects của các classes khác nhau được xử lý như objects của một base class hoặc interface chung. Cùng một method call có thể hoạt động khác nhau tùy thuộc vào loại object thực tế.

Ý tưởng cốt lõi

Polymorphism cho phép bạn viết code hoạt động với objects ở mức trừu tượng mà không cần biết các types cụ thể của chúng. Điều này làm cho code linh hoạt và dễ mở rộng hơn.

Ví dụ: Polymorphism Runtime

// Interface định nghĩa contract
interface Shape {
  calculateArea(): number;
  draw(): void;
}
 
class Circle implements Shape {
  constructor(private radius: number) {}
 
  calculateArea(): number {
    return Math.PI * this.radius * this.radius;
  }
 
  draw(): void {
    console.log(`Vẽ hình tròn với bán kính ${this.radius}`);
  }
}
 
class Rectangle implements Shape {
  constructor(private width: number, private height: number) {}
 
  calculateArea(): number {
    return this.width * this.height;
  }
 
  draw(): void {
    console.log(`Vẽ hình chữ nhật ${this.width}x${this.height}`);
  }
}
 
// Hàm polymorphic - hoạt động với BẤT KỲ Shape nào
function printShapeInfo(shape: Shape): void {
  console.log(`Diện tích: ${shape.calculateArea().toFixed(2)}`);
  shape.draw();
}
 
// Sử dụng - hành vi khác nhau, cùng interface
const shapes: Shape[] = [
  new Circle(5),
  new Rectangle(4, 6)
];
 
shapes.forEach(shape => {
  printShapeInfo(shape);
  console.log("---");
});
 
// Output:
// Diện tích: 78.54
// Vẽ hình tròn với bán kính 5
// ---
// Diện tích: 24.00
// Vẽ hình chữ nhật 4x6
// ---

Ví dụ thực tế: Xử lý thanh toán

interface PaymentProcessor {
  processPayment(amount: number): boolean;
}
 
class CreditCardProcessor implements PaymentProcessor {
  processPayment(amount: number): boolean {
    console.log(`Xử lý $${amount} qua Thẻ Tín Dụng...`);
    return true;
  }
}
 
class PayPalProcessor implements PaymentProcessor {
  processPayment(amount: number): boolean {
    console.log(`Xử lý $${amount} qua PayPal...`);
    return true;
  }
}
 
class CryptoProcessor implements PaymentProcessor {
  processPayment(amount: number): boolean {
    console.log(`Xử lý $${amount} qua Crypto...`);
    return true;
  }
}
 
// Checkout service - hoạt động với BẤT KỲ PaymentProcessor nào
class CheckoutService {
  private paymentProcessor: PaymentProcessor;
 
  constructor(processor: PaymentProcessor) {
    this.paymentProcessor = processor;
  }
 
  // Có thể đổi processors mà không thay đổi code này!
  setPaymentProcessor(processor: PaymentProcessor): void {
    this.paymentProcessor = processor;
  }
 
  checkout(amount: number): boolean {
    console.log(`Bắt đầu checkout cho $${amount}`);
    return this.paymentProcessor.processPayment(amount);
  }
}
 
// Sử dụng - linh hoạt tại runtime
const checkout = new CheckoutService(new CreditCardProcessor());
checkout.checkout(100);
 
// Chuyển sang PayPal tại runtime
checkout.setPaymentProcessor(new PayPalProcessor());
checkout.checkout(50);
 
// Chuyển sang Crypto
checkout.setPaymentProcessor(new CryptoProcessor());
checkout.checkout(200);

Lợi ích của Polymorphism

  1. Linh hoạt: Thêm implementations mới mà không thay đổi code hiện có
  2. Mở rộng: Dễ dàng mở rộng hệ thống (Open/Closed Principle)
  3. Loose Coupling: Client code phụ thuộc vào abstractions, không phải concretions
  4. Dễ test: Dễ mock implementations cho testing
  5. Code sạch: Loại bỏ logic điều kiện phức tạp

Trụ Cột 4: Abstraction (Trừu tượng)

Abstraction là quá trình ẩn các chi tiết implementation phức tạp và chỉ hiển thị các tính năng thiết yếu của một object. Nó tập trung vào cái gì object làm thay vì như thế nào nó làm.

Ý tưởng cốt lõi

Hãy nghĩ về remote TV. Bạn nhấn "Tăng Âm Lượng" mà không cần biết tín hiệu hồng ngoại hoạt động như thế nào, TV nhận nó như thế nào, hoặc loa khuếch đại âm thanh ra sao. Các chi tiết phức tạp được trừu tượng hóa phía sau một interface đơn giản.

Abstract Classes vs Interfaces

Khía cạnhAbstract ClassInterface
ImplementationCó thể có một số method implementationsKhông có implementation (truyền thống)
StateCó thể có instance variablesKhông có state (chỉ constants)
InheritanceSingle inheritanceMultiple implementation
ConstructorCó thể có constructorKhông có constructor
Sử dụng khiChia sẻ code giữa các classes liên quanĐịnh nghĩa contract cho các classes không liên quan

Ví dụ: Interfaces cho Abstraction

// Interface định nghĩa logger làm gì, không phải cách nó làm
interface ILogger {
  debug(message: string): void;
  info(message: string): void;
  warn(message: string): void;
  error(message: string, error?: Error): void;
}
 
// Console implementation
class ConsoleLogger implements ILogger {
  debug(message: string): void {
    console.debug(`[DEBUG] ${message}`);
  }
 
  info(message: string): void {
    console.info(`[INFO] ${message}`);
  }
 
  warn(message: string): void {
    console.warn(`[WARN] ${message}`);
  }
 
  error(message: string, error?: Error): void {
    console.error(`[ERROR] ${message}`, error);
  }
}
 
// File implementation
class FileLogger implements ILogger {
  private filePath: string;
 
  constructor(filePath: string) {
    this.filePath = filePath;
  }
 
  debug(message: string): void {
    this.writeToFile("DEBUG", message);
  }
 
  info(message: string): void {
    this.writeToFile("INFO", message);
  }
 
  warn(message: string): void {
    this.writeToFile("WARN", message);
  }
 
  error(message: string, error?: Error): void {
    this.writeToFile("ERROR", error ? `${message}: ${error.message}` : message);
  }
 
  private writeToFile(level: string, message: string): void {
    console.log(`Ghi vào ${this.filePath}: [${level}] ${message}`);
  }
}
 
// Application sử dụng abstraction, không phải concrete implementations
class UserService {
  private logger: ILogger;
 
  constructor(logger: ILogger) {
    this.logger = logger;
  }
 
  createUser(name: string, email: string): void {
    this.logger.info(`Tạo user: ${name}`);
    // Logic tạo user
    this.logger.info(`User đã được tạo thành công: ${email}`);
  }
}
 
// Sử dụng - dễ dàng đổi implementations
const consoleLogger = new ConsoleLogger();
const fileLogger = new FileLogger("/var/log/app.log");
 
// Development: dùng console
const devService = new UserService(consoleLogger);
devService.createUser("John", "[email protected]");
 
// Production: dùng file logging
const prodService = new UserService(fileLogger);
prodService.createUser("Jane", "[email protected]");

Lợi ích của Abstraction

  1. Đơn giản: Ẩn các chi tiết phức tạp khỏi users
  2. Linh hoạt: Thay đổi implementation mà không ảnh hưởng clients
  3. Dễ bảo trì: Thay đổi cô lập không lan tỏa
  4. Dễ test: Dễ mock abstract interfaces
  5. Bảo mật: Các chi tiết nội bộ được ẩn

Kết hợp tất cả: Ví dụ thực tế

Hãy kết hợp cả bốn trụ cột trong một ví dụ toàn diện: hệ thống thông báo e-commerce.

// ============================================
// ABSTRACTION: Định nghĩa notifications làm gì
// ============================================
interface INotification {
  send(recipient: string, subject: string, body: string): Promise<boolean>;
}
 
// ============================================
// ENCAPSULATION: Bảo vệ state nội bộ
// ============================================
abstract class BaseNotification implements INotification {
  private readonly id: string;
  private status: "pending" | "sent" | "failed" = "pending";
  protected retryCount: number = 0;
  protected readonly maxRetries: number = 3;
 
  constructor() {
    this.id = `NOTIF-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
  }
 
  protected setStatus(status: "pending" | "sent" | "failed"): void {
    this.status = status;
  }
 
  async send(recipient: string, subject: string, body: string): Promise<boolean> {
    try {
      console.log(`[${this.id}] Chuẩn bị gửi...`);
      const success = await this.doSend(recipient, subject, body);
      if (success) {
        this.setStatus("sent");
        console.log(`[${this.id}] Gửi thành công`);
      }
      return success;
    } catch (error) {
      console.error(`[${this.id}] Gửi thất bại:`, error);
      this.setStatus("failed");
      return false;
    }
  }
 
  protected abstract doSend(recipient: string, subject: string, body: string): Promise<boolean>;
}
 
// ============================================
// INHERITANCE: Tái sử dụng logic notification chung
// ============================================
class EmailNotification extends BaseNotification {
  private smtpServer: string;
  private fromAddress: string;
 
  constructor(smtpServer: string, fromAddress: string) {
    super();
    this.smtpServer = smtpServer;
    this.fromAddress = fromAddress;
  }
 
  protected async doSend(recipient: string, subject: string, body: string): Promise<boolean> {
    console.log(`Gửi email qua ${this.smtpServer}`);
    console.log(`Từ: ${this.fromAddress}, Đến: ${recipient}`);
    console.log(`Chủ đề: ${subject}`);
    return true;
  }
}
 
class SMSNotification extends BaseNotification {
  private provider: string;
 
  constructor(provider: string) {
    super();
    this.provider = provider;
  }
 
  protected async doSend(recipient: string, subject: string, body: string): Promise<boolean> {
    console.log(`Gửi SMS qua ${this.provider}`);
    console.log(`Đến: ${recipient}, Tin nhắn: ${body}`);
    return true;
  }
}
 
// ============================================
// POLYMORPHISM: Hoạt động với bất kỳ notification type nào
// ============================================
class NotificationService {
  private notifications: INotification[] = [];
 
  registerNotificationChannel(notification: INotification): void {
    this.notifications.push(notification);
  }
 
  async sendToAll(recipient: string, subject: string, body: string): Promise<void> {
    console.log("=== Gửi thông báo đến tất cả kênh ===\n");
 
    for (const notification of this.notifications) {
      await notification.send(recipient, subject, body);
      console.log("---");
    }
  }
}
 
// Sử dụng
const emailNotification = new EmailNotification("smtp.example.com", "[email protected]");
const smsNotification = new SMSNotification("Twilio");
 
const service = new NotificationService();
service.registerNotificationChannel(emailNotification);
service.registerNotificationChannel(smsNotification);
 
service.sendToAll(
  "[email protected]",
  "Xác nhận đơn hàng",
  "Đơn hàng #12345 của bạn đã được xác nhận!"
);

Cách mỗi Trụ Cột đóng góp

Trụ CộtCách sử dụng
AbstractionInterface INotification định nghĩa notifications làm gì, không phải cách làm
EncapsulationBaseNotification bảo vệ id, status với private fields
InheritanceEmailNotification, SMSNotification mở rộng BaseNotification
PolymorphismNotificationService.sendToAll() hoạt động với bất kỳ INotification implementation nào

Tổng Kết và Điểm Chính

Tóm tắt Bốn Trụ Cột

Trụ CộtĐịnh nghĩaGhi nhớ
EncapsulationGói dữ liệu + methods, ẩn state nội bộ"Bảo vệ dữ liệu của bạn"
InheritanceTạo phân cấp classes cho quan hệ "is-a""Mở rộng, đừng sửa đổi"
PolymorphismMột interface, nhiều implementations"Lập trình theo interfaces"
AbstractionẨn phức tạp, hiển thị tính năng thiết yếu"Đơn giản hóa cái phức tạp"

Best Practices

  1. Bắt đầu với abstraction: Định nghĩa interfaces trước implementations
  2. Encapsulate mặc định: Làm mọi thứ private trừ khi cần thiết
  3. Ưu tiên composition hơn inheritance: Chỉ dùng inheritance cho "is-a" thực sự
  4. Tận dụng polymorphism: Loại bỏ type checking có điều kiện
  5. Giữ phân cấp nông: Tối đa 2-3 cấp
  6. Tuân theo SOLID principles: Chúng củng cố bốn trụ cột

Tiếp Theo Là Gì?

Bây giờ bạn đã hiểu bốn trụ cột, bạn sẵn sàng đi sâu hơn:

  1. Classes, Objects và Abstraction: Đào sâu về abstraction
  2. Encapsulation và Information Hiding: Làm chủ bảo vệ dữ liệu
  3. Inheritance và Composition: Chọn cách tiếp cận đúng
  4. Polymorphism và Interfaces: Thiết kế linh hoạt
  5. SOLID Principles: Thiết kế OOP sạch

Bài Tập Thực Hành

Bài Tập 1: Hệ Thống Tài Khoản Ngân Hàng

Tạo hệ thống ngân hàng với:

  • Abstract class Account với encapsulated balance
  • Concrete classes: SavingsAccount, CheckingAccount, InvestmentAccount
  • Polymorphic method calculateInterest() (lãi suất khác nhau cho mỗi loại tài khoản)

Bài Tập 2: Shape Calculator

Xây dựng máy tính hình học:

  • Interface IShape với methods area() và perimeter()
  • Implement ít nhất 4 hình
  • Class ShapeCalculator hoạt động với bất kỳ IShape nào

Bài Tập 3: Hệ Thống Thông Báo

Mở rộng ví dụ notification để:

  • Thêm class SlackNotification
  • Implement retry với exponential backoff
  • Thêm MultiChannelNotification gửi đến nhiều kênh

Tài Nguyên


Có câu hỏi hoặc feedback? Đừng ngại liên hệ tại [email protected] hoặc kết nối với tôi trên X.

Chúc bạn code vui vẻ! 🚀

📬 Đă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.