OOP - Tính Đóng gói và Tính Kế thừa

Đăng ngày: 29-08-2025 12:00
học javalập trình javajava ooptính đóng góiencapsulationtính kế thừainheritanceextendsprivatepublicgettersetter

Chào mừng bạn đến với bài 8 của series "Chinh Phục Java"! Ở bài trước, chúng ta đã tìm hiểu về Lớp và Đối tượng. Đó là những khái niệm cơ bản của Lập trình Hướng đối tượng (OOP). Hôm nay, chúng ta sẽ khám phá sâu hơn về các nguyên tắc giúp OOP trở nên mạnh mẽ. OOP được xây dựng dựa trên bốn trụ cột chính. Trong bài này, chúng ta sẽ tìm hiểu hai trụ cột đầu tiên: **Tính đóng gói (Encapsulation)** và **Tính kế thừa (Inheritance)**.

1. Tính Đóng gói (Encapsulation) - Che giấu và Bảo vệ Dữ liệu

Hãy tưởng tượng một chiếc TV. Bạn có thể sử dụng điều khiển từ xa để bật/tắt, chuyển kênh. Bạn không cần biết các mạch điện tử bên trong hoạt động như thế nào. Nhà sản xuất đã "đóng gói" sự phức tạp đó lại. Họ chỉ cung cấp cho bạn một giao diện đơn giản để tương tác. **Tính đóng gói** trong OOP cũng hoạt động tương tự.

Nguyên tắc này nói rằng: dữ liệu (thuộc tính) của một đối tượng nên được **che giấu (hidden)** khỏi thế giới bên ngoài. Việc truy cập và thay đổi dữ liệu đó phải được thực hiện thông qua các phương thức công khai (public methods). Điều này giúp bảo vệ dữ liệu khỏi những thay đổi không hợp lệ. Nó cũng giúp bạn dễ dàng thay đổi logic xử lý bên trong mà không ảnh hưởng đến các phần khác của chương trình.

Trong Java, chúng ta thực hiện tính đóng gói bằng cách:

  1. Khai báo các thuộc tính với từ khóa private.
  2. Cung cấp các phương thức public để truy cập và thay đổi các thuộc tính đó. Các phương thức này thường được gọi là Getter và Setter.

// File: BankAccount.java
public class BankAccount {
    // 1. Thuộc tính được khai báo là private
    private String accountNumber;
    private double balance;

    public BankAccount(String accountNumber, double initialBalance) {
        this.accountNumber = accountNumber;
        this.balance = initialBalance;
    }

    // 2. Cung cấp các phương thức public (Getters & Setters)
    
    // Getter: Chỉ cho phép đọc (lấy) số dư
    public double getBalance() {
        return this.balance;
    }

    // Phương thức public để nạp tiền
    public void deposit(double amount) {
        if (amount > 0) {
            this.balance += amount;
            System.out.println("Nạp thành công: " + amount);
        } else {
            System.out.println("Số tiền nạp phải lớn hơn 0.");
        }
    }

    // Phương thức public để rút tiền
    public void withdraw(double amount) {
        if (amount > 0 && amount <= this.balance) {
            this.balance -= amount;
            System.out.println("Rút thành công: " + amount);
        } else {
            System.out.println("Giao dịch không hợp lệ.");
        }
    }
}

Trong ví dụ trên, bạn không thể trực tiếp thay đổi `balance` từ bên ngoài. Mọi thay đổi phải thông qua `deposit()` và `withdraw()`. Điều này cho phép chúng ta thêm logic kiểm tra, đảm bảo số dư không bao giờ bị âm một cách vô lý.

OOP - Tính Đóng gói và Tính Kế thừa - 0

2. Tính Kế thừa (Inheritance) - Tái sử dụng và Mở rộng

Trong thế giới thực, có rất nhiều mối quan hệ "cha-con". Ví dụ, "Chó" là một loại "Động vật". "Xe hơi" là một loại "Phương tiện giao thông". Một con chó có tất cả các đặc điểm của một động vật (ăn, ngủ). Ngoài ra, nó còn có các hành vi riêng (sủa). **Tính kế thừa** trong OOP cho phép chúng ta mô hình hóa mối quan hệ này.

Kế thừa cho phép một lớp (gọi là **lớp con - subclass/child class**) thừa hưởng các thuộc tính và phương thức từ một lớp khác (gọi là **lớp cha - superclass/parent class**). Lớp con có thể sử dụng lại code của lớp cha. Nó cũng có thể thêm các thuộc tính, phương thức mới hoặc thay đổi hành vi của lớp cha. Chúng ta sử dụng từ khóa extends để thực hiện kế thừa.

Hãy xem ví dụ về lớp `Animal` và các lớp con của nó.


// File: Animal.java (Lớp cha)
public class Animal {
    String name;

    public void eat() {
        System.out.println(name + " đang ăn...");
    }

    public void sleep() {
        System.out.println(name + " đang ngủ...");
    }
}

// File: Dog.java (Lớp con kế thừa từ Animal)
public class Dog extends Animal {
    // Lớp Dog tự động có thuộc tính 'name' và phương thức 'eat()', 'sleep()'

    // Thêm một phương thức riêng cho Dog
    public void bark() {
        System.out.println("Gâu gâu!");
    }
}

// File: Cat.java (Lớp con kế thừa từ Animal)
public class Cat extends Animal {
    // Thêm một phương thức riêng cho Cat
    public void meow() {
        System.out.println("Meo meo!");
    }
}

Bây giờ, chúng ta có thể sử dụng các lớp này như sau:


public class Zoo {
    public static void main(String[] args) {
        Dog myDog = new Dog();
        myDog.name = "Milu";
        myDog.eat();    // <-- Phương thức được kế thừa từ Animal
        myDog.sleep();  // <-- Phương thức được kế thừa từ Animal
        myDog.bark();   // <-- Phương thức riêng của Dog

        Cat myCat = new Cat();
        myCat.name = "Tom";
        myCat.eat();    // <-- Phương thức được kế thừa từ Animal
        myCat.meow();   // <-- Phương thức riêng của Cat
    }
}

Kết luận

Bạn vừa tìm hiểu hai trong số những nguyên tắc mạnh mẽ nhất của OOP. Tính đóng gói giúp bạn bảo vệ dữ liệu và che giấu sự phức tạp. Tính kế thừa giúp bạn tái sử dụng code và xây dựng một hệ thống phân cấp logic. Việc nắm vững hai khái niệm này sẽ giúp code của bạn an toàn và có cấu trúc tốt hơn. Ở bài tiếp theo, chúng ta sẽ khám phá hai trụ cột còn lại: Đa hình và Trừu tượng.

0 Bình luận