# Bài 2: Encapsulation (Đóng gói) trong Java

#### I. Định nghĩa và lợi ích của Encapsulation

**Định nghĩa Encapsulation:**

Encapsulation (Đóng gói) là quá trình gói gọn dữ liệu (các biến) và các phương thức thao tác trên dữ liệu đó trong một đơn vị duy nhất, thường là một lớp (class). Dữ liệu của đối tượng được bảo vệ khỏi sự truy cập không hợp lệ từ bên ngoài bằng cách sử dụng các access modifier như `private`, `protected`, và `public`.

**Lợi ích của Encapsulation:**

* **Bảo vệ dữ liệu:** Ngăn chặn sự truy cập và thay đổi dữ liệu trực tiếp từ bên ngoài. Dữ liệu chỉ có thể được truy cập và thay đổi thông qua các phương thức được chỉ định (getter và setter).
    
* **Kiểm soát truy cập:** Chỉ cho phép các thành phần của đối tượng hoặc lớp khác truy cập dữ liệu một cách kiểm soát thông qua các phương thức getter và setter.
    
* **Tăng tính bảo trì:** Khi cần thay đổi cấu trúc dữ liệu nội bộ, bạn chỉ cần thay đổi trong lớp, không ảnh hưởng đến mã nguồn bên ngoài.
    
* **Ẩn chi tiết triển khai:** Chỉ tiết lộ các chi tiết cần thiết cho người dùng và ẩn các chi tiết cài đặt không cần thiết.
    

#### II. Cách thực hiện Encapsulation trong Java

**Sử dụng** `private` access modifier:

Biến instance (thuộc tính) của lớp được khai báo là `private` để ngăn chặn sự truy cập trực tiếp từ bên ngoài lớp.

**Sử dụng getter và setter:**

Cung cấp các phương thức `getter` để lấy giá trị của biến và `setter` để thay đổi giá trị của biến. Các phương thức này thường được khai báo là `public` để có thể truy cập từ bên ngoài.

**Ví dụ cụ thể:**

```java
public class Person {
    private String name;
    private int age;

    // Constructor
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // Getter for name
    public String getName() {
        return name;
    }

    // Setter for name
    public void setName(String name) {
        this.name = name;
    }

    // Getter for age
    public int getAge() {
        return age;
    }

    // Setter for age
    public void setAge(int age) {
        if (age > 0) {
            this.age = age;
        } else {
            System.out.println("Age must be positive");
        }
    }
}
```

Trong ví dụ trên, các biến `name` và `age` được khai báo là `private`, do đó chỉ có thể truy cập thông qua các phương thức `getName`, `setName`, `getAge`, và `setAge`.

#### III. Ví dụ minh họa

**Tạo lớp với các thuộc tính** `private`:

```java
public class BankAccount {
    private String accountNumber;
    private double balance;

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

    // Getter for accountNumber
    public String getAccountNumber() {
        return accountNumber;
    }

    // Setter for accountNumber
    public void setAccountNumber(String accountNumber) {
        this.accountNumber = accountNumber;
    }

    // Getter for balance
    public double getBalance() {
        return balance;
    }

    // Setter for balance
    public void setBalance(double balance) {
        if (balance >= 0) {
            this.balance = balance;
        } else {
            System.out.println("Balance cannot be negative");
        }
    }

    // Method to deposit money
    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
            System.out.println("Deposited: " + amount);
        } else {
            System.out.println("Deposit amount must be positive");
        }
    }

    // Method to withdraw money
    public void withdraw(double amount) {
        if (amount > 0 && amount <= balance) {
            balance -= amount;
            System.out.println("Withdrew: " + amount);
        } else {
            System.out.println("Invalid withdrawal amount");
        }
    }
}
```

**Sử dụng các phương thức** `getter` và `setter`:

```java
public class Main {
    public static void main(String[] args) {
        BankAccount account = new BankAccount("123456789", 1000.0);
        
        // Sử dụng getter để lấy thông tin
        System.out.println("Account Number: " + account.getAccountNumber());
        System.out.println("Balance: " + account.getBalance());
        
        // Sử dụng setter để thay đổi thông tin
        account.setAccountNumber("987654321");
        account.setBalance(1500.0);

        System.out.println("Updated Account Number: " + account.getAccountNumber());
        System.out.println("Updated Balance: " + account.getBalance());

        // Gửi tiền vào tài khoản
        account.deposit(500.0);
        System.out.println("Balance after deposit: " + account.getBalance());

        // Rút tiền khỏi tài khoản
        account.withdraw(300.0);
        System.out.println("Balance after withdrawal: " + account.getBalance());
    }
}
```

#### IV. Bài tập thực hành

**Bài tập:** Tạo một lớp `Person` với các thuộc tính được đóng gói và các phương thức getter và setter.

**Yêu cầu:**

1. Tạo lớp `Person` với các thuộc tính `name`, `age`, và `address`.
    
2. Đóng gói các thuộc tính bằng cách sử dụng access modifier `private`.
    
3. Cung cấp các phương thức getter và setter cho các thuộc tính này.
    
4. Viết một chương trình chính để tạo đối tượng `Person`, thay đổi và truy xuất thông tin thông qua các phương thức getter và setter.
    

**Mã nguồn mẫu:**

```java
public class Person {
    private String name;
    private int age;
    private String address;

    // Constructor
    public Person(String name, int age, String address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    // Getter for name
    public String getName() {
        return name;
    }

    // Setter for name
    public void setName(String name) {
        this.name = name;
    }

    // Getter for age
    public int getAge() {
        return age;
    }

    // Setter for age
    public void setAge(int age) {
        if (age > 0) {
            this.age = age;
        } else {
            System.out.println("Age must be positive");
        }
    }

    // Getter for address
    public String getAddress() {
        return address;
    }

    // Setter for address
    public void setAddress(String address) {
        this.address = address;
    }

    public static void main(String[] args) {
        Person person = new Person("Alice", 30, "123 Main St");

        // Sử dụng getter để lấy thông tin
        System.out.println("Name: " + person.getName());
        System.out.println("Age: " + person.getAge());
        System.out.println("Address: " + person.getAddress());

        // Sử dụng setter để thay đổi thông tin
        person.setName("Bob");
        person.setAge(25);
        person.setAddress("456 Oak St");

        System.out.println("Updated Name: " + person.getName());
        System.out.println("Updated Age: " + person.getAge());
        System.out.println("Updated Address: " + person.getAddress());
    }
}
```

#### Tổng kết

Bài học này cung cấp một cái nhìn chi tiết về Encapsulation (Đóng gói) trong Java, bao gồm định nghĩa, lợi ích, cách thực hiện, và các ví dụ minh họa. Việc nắm vững khái niệm này sẽ giúp bạn bảo vệ dữ liệu và tăng cường tính bảo trì của mã nguồn trong các dự án phần mềm.
