Đào sâu về 4 trụ cột OOP trong Java: Thực tế và Ứng dụng
Đào sâu về 4 trụ cột OOP trong Java
Lập trình hướng đối tượng (OOP) thường bị hiểu nhầm là chỉ xoay quanh việc tạo class. Thực tế, nó là một tư duy tổ chức mã nguồn giúp hệ thống linh hoạt và dễ mở rộng.
Nếu bạn chỉ dừng ở mức “biết dùng class” thì code vẫn chạy được, nhưng rất khó mở rộng, khó test và dễ “vỡ trận” khi yêu cầu nghiệp vụ thay đổi. Mục tiêu của bài viết này là đưa bạn từ mức “biết OOP” sang mức “dùng OOP một cách có chiến lược”.
1. Tính Đóng Gói (Encapsulation) - Bảo mật trạng thái
Tính đóng gói không chỉ là việc đặt biến là private và tạo getter/setter. Mục tiêu thực sự là bảo vệ tính toàn vẹn của dữ liệu, và ép mọi tương tác phải đi qua một “cổng” có kiểm soát.
Ví dụ, nếu bạn có một lớp BankAccount, bạn không muốn ai đó thay đổi số dư trực tiếp. Thay vào đó, bạn cung cấp phương thức deposit() để kiểm tra tính hợp lệ trước khi cộng tiền.
public class BankAccount {
private double balance;
public void deposit(double amount) {
if (amount > 0) {
this.balance += amount;
System.out.println("Nạp tiền thành công: " + amount);
} else {
throw new IllegalArgumentException("Số tiền nạp phải lớn hơn 0");
}
}
}
Trong các hệ thống lớn, tính đóng gói còn liên quan đến module boundaries: bạn quyết định những gì được public trong package, những gì bị ẩn đi. Điều này giúp đội ngũ dễ thay đổi implement bên trong mà không phá vỡ code của module khác.
2. Tính Kế Thừa (Inheritance) - Tái sử dụng hay là "Cái bẫy"?
Kế thừa giúp tái sử dụng mã nguồn, nhưng nếu lạm dụng sẽ dẫn đến "mối liên kết chặt chẽ" (tight coupling). Trong Java, chúng ta nên ưu tiên "Composition over Inheritance" (Ưu tiên thành phần hơn kế thừa).
Ví dụ, thay vì tạo một class LoggingList extends ArrayList, bạn có thể bọc (wrap) một List bên trong một class riêng, và chỉ expose những hành vi cần thiết. Cách này giảm rủi ro khi JDK thay đổi implement nội bộ của ArrayList.
Khi nào nên dùng kế thừa?
- Khi bạn thực sự có mối quan hệ "is-a" (là một loại của).
- Khi class cha được thiết kế để mở rộng (có
protected, template method, hoặc là một abstract base class). - Khi bạn kiểm soát được cả class cha và class con (trong cùng dự án, cùng team).
3. Tính Đa Hình (Polymorphism) - Sức mạnh của sự linh hoạt
Đa hình cho phép chúng ta xử lý các đối tượng khác nhau thông qua một giao diện chung. Điều này cực kỳ hữu ích trong việc xây dựng các plugin hoặc xử lý nhiều loại thanh toán (Credit Card, PayPal, Crypto) mà không cần thay đổi code xử lý chính.
Ví dụ, bạn có thể định nghĩa một interface PaymentMethod với phương thức pay(). Mọi phương thức thanh toán (VNPay, Momo, Stripe,...) chỉ cần implement interface này, còn luồng checkout chính chỉ biết gọi paymentMethod.pay().
Điều này giúp hệ thống dễ dàng mở rộng: thêm phương thức thanh toán mới mà không chạm vào logic core, tránh vi phạm Open/Closed Principle.
4. Tính Trừu Tượng (Abstraction) - Tập trung vào "Cái gì", không phải "Làm thế nào"
Trừu tượng hóa giúp ẩn đi sự phức tạp của hệ thống. Hãy tưởng tượng bạn đang lái xe, bạn chỉ cần biết cách sử dụng vô lăng (Interface) mà không cần quan tâm động cơ bên trong hoạt động ra sao.
Trong thực tế, abstraction thể hiện qua:
- Interface và abstract class.
- Các service layer che giấu repository/DAO bên dưới.
- Các SDK/API client che giấu chi tiết giao tiếp mạng.
Một thiết kế tốt thường bắt đầu bằng việc đặt ra các abstraction hợp lý, sau đó mới đi vào implement cụ thể.
5. Áp dụng OOP trong kiến trúc thực tế
Trong các ứng dụng enterprise, 4 trụ cột OOP không đứng riêng lẻ mà kết hợp với các pattern như:
- Strategy Pattern: tận dụng đa hình để hoán đổi thuật toán runtime.
- Template Method: trừu tượng hóa skeleton của thuật toán và cho phép subclass override từng bước nhỏ.
- Decorator: dùng composition để mở rộng hành vi mà không cần kế thừa.
Khi áp dụng đúng, hệ thống sẽ:
- Dễ đọc hơn: business logic được đặt đúng nơi, ít “if/else rừng rậm”.
- Dễ test hơn: có thể mock interface để test từng phần.
- Dễ mở rộng hơn: thêm tính năng bằng cách thêm class mới thay vì sửa class cũ.
Kết luận: Nắm vững OOP giúp bạn viết code không chỉ chạy được, mà còn "sống khỏe" qua nhiều năm bảo trì và thay đổi yêu cầu nghiệp vụ.