Bài 7: CONSTRUCTOR VÀ TÍNH ĐÓNG GÓI – BẢO VỆ DỮ LIỆU THÔNG MINH

Chào mừng các bạn trở lại! Hôm nay chúng ta sẽ học hai khái niệm quan trọng giúp code Java của bạn trở nên chuyên nghiệp và an toàn hơn: Constructor và Tính đóng gói (Encapsulation).

I. PHẦN LÝ THUYẾT

1. Vấn đề với cách tạo đối tượng cũ

Ở bài trước, chúng ta tạo đối tượng như thế này:

SinhVien sv = new SinhVien();
sv.ten = "Nguyễn Văn A";  // Phải gán từng thuộc tính
sv.tuoi = 20;
sv.maSV = "SV001";

Vấn đề:

  • Dễ quên gán giá trị cho thuộc tính
  • Không kiểm soát được giá trị hợp lệ
  • Code dài dòng, dễ sai sót

2. Constructor – “Nhà xây dựng” thông minh

Constructor là phương thức đặc biệt:

  • Tên trùng với tên lớp
  • Không có kiểu trả về (không có void hay return type)
  • Được gọi tự động khi tạo đối tượng với new
  • Có thể có nhiều constructor (overloading)
public class SinhVien {
    String ten;
    int tuoi;
    String maSV;
    
    // Constructor mặc định (không tham số)
    public SinhVien() {
        System.out.println("Đã tạo một sinh viên mới!");
    }
    
    // Constructor có tham số
    public SinhVien(String ten, int tuoi, String maSV) {
        this.ten = ten;
        this.tuoi = tuoi;
        this.maSV = maSV;
    }
}

3. Từ khóa this – Đại diện cho đối tượng hiện tại

this tham chiếu đến đối tượng hiện tại đang được xử lý. Nó giúp:

  • Phân biệt thuộc tính và tham số cùng tên
  • Truy cập thuộc tính của đối tượng hiện tại
  • Gọi constructor khác trong cùng lớp

4. Tính đóng gói (Encapsulation) – Nguyên lý bảo mật

Tính đóng gói là nguyên lý:

  • Ẩn dữ liệu (thuộc tính) bằng private
  • Cung cấp phương thức công khai (public) để truy cập an toàn
  • Kiểm soát và validate dữ liệu trước khi gán

Tại sao cần đóng gói?

  • Bảo vệ dữ liệu khỏi truy cập trái phép
  • Kiểm soát tính hợp lệ của dữ liệu
  • Dễ dàng thay đổi cài đặt bên trong mà không ảnh hưởng code bên ngoài

5. Access Modifiers – Các mức độ truy cập

ModifierTrong lớpTrong packageLớp conMọi nơi
private✓✗✗✗
default✓✓✗✗
protected✓✓✓✗
public✓✓✓✓

II. PHẦN THỰC HÀNH

Ví dụ 1:

  • Định nghĩa Lớp SinhVien với Constructor và Encapsulation (SinhVien.java)
  • Sử dụng lớp (MainSinhVien.java)
// file  SinhVien.java
public class SinhVien {
    // Thuộc tính PRIVATE - chỉ truy cập trong lớp
    private String ten;
    private int tuoi;
    private String maSV;
    private double diemTB;
    
    // Constructor mặc định
    public SinhVien() {
        this.ten = "Chưa có tên";
        this.tuoi = 18;
        this.maSV = "SV000";
        this.diemTB = 0.0;
    }
    
    // Constructor có tham số
    public SinhVien(String ten, int tuoi, String maSV) {
        this.ten = ten;
        this.setTuoi(tuoi);  // Sử dụng setter để validate
        this.maSV = maSV;
        this.diemTB = 0.0;
    }
    
    // GETTER - Lấy giá trị thuộc tính
    public String getTen() {
        return ten;
    }
    
    public int getTuoi() {
        return tuoi;
    }
    
    public String getMaSV() {
        return maSV;
    }
    
    public double getDiemTB() {
        return diemTB;
    }
    
    // SETTER - Gán giá trị với kiểm tra
    public void setTen(String ten) {
        if (ten != null && !ten.trim().isEmpty()) {
            this.ten = ten;
        } else {
            System.out.println("Tên không hợp lệ!");
        }
    }
    
    public void setTuoi(int tuoi) {
        if (tuoi >= 18 && tuoi <= 50) {
            this.tuoi = tuoi;
        } else {
            System.out.println("Tuổi phải từ 18 đến 50!");
            this.tuoi = 18;  // Giá trị mặc định
        }
    }
    
    public void setMaSV(String maSV) {
        if (maSV != null && maSV.startsWith("SV")) {
            this.maSV = maSV;
        } else {
            System.out.println("Mã SV phải bắt đầu bằng 'SV'!");
        }
    }
    
    public void setDiemTB(double diemTB) {
        if (diemTB >= 0.0 && diemTB <= 10.0) {
            this.diemTB = diemTB;
        } else {
            System.out.println("Điểm TB phải từ 0.0 đến 10.0!");
        }
    }
    
    // Phương thức công khai
    public void hienThiThongTin() {
        System.out.println("=== THÔNG TIN SINH VIÊN ===");
        System.out.println("Tên: " + this.ten);
        System.out.println("Tuổi: " + this.tuoi);
        System.out.println("Mã SV: " + this.maSV);
        System.out.println("Điểm TB: " + this.diemTB);
        System.out.println("Xếp loại: " + this.xepLoai());
    }
    
    // Phương thức private - chỉ dùng nội bộ
    private String xepLoai() {
        if (diemTB >= 8.0) return "Giỏi";
        else if (diemTB >= 6.5) return "Khá";
        else if (diemTB >= 5.0) return "Trung bình";
        else return "Yếu";
    }
}
// Chương trình chính: File  MainSinhVien.java
public class MainSinhVien {
    public static void main(String[] args) {
        // Tạo sinh viên với constructor mặc định
        SinhVien sv1 = new SinhVien();
        sv1.setTen("Nguyễn Văn A");
        sv1.setTuoi(20);
        sv1.setMaSV("SV001");
        sv1.setDiemTB(8.5);
        sv1.hienThiThongTin();
        
        System.out.println("\n---\n");
        
        // Tạo sinh viên với constructor có tham số
        SinhVien sv2 = new SinhVien("Trần Thị B", 21, "SV002");
        sv2.setDiemTB(7.2);
        sv2.hienThiThongTin();
        
        System.out.println("\n---\n");
        
        // Test validation
        SinhVien sv3 = new SinhVien();
        sv3.setTuoi(15);  // Tuổi không hợp lệ
        sv3.setDiemTB(12.5);  // Điểm không hợp lệ
        sv3.setMaSV("ABC123");  // Mã SV không hợp lệ
        
        // Truy cập thông qua getter
        System.out.println("\nTên sv1: " + sv1.getTen());
        System.out.println("Điểm TB sv2: " + sv2.getDiemTB());
    }
}

Ví dụ 2: Lớp TaiKhoanNganHang với Constructor Overloading

//TaiKhoanNganHang.java 
public class TaiKhoanNganHang {
    private String soTaiKhoan;
    private String tenChuTaiKhoan;
    private double soDu;
    
    // Constructor 1: Chỉ có số tài khoản
    public TaiKhoanNganHang(String soTaiKhoan) {
        this.soTaiKhoan = soTaiKhoan;
        this.tenChuTaiKhoan = "Chưa xác định";
        this.soDu = 0.0;
    }
    
    // Constructor 2: Có số tài khoản và tên chủ
    public TaiKhoanNganHang(String soTaiKhoan, String tenChuTaiKhoan) {
        this.soTaiKhoan = soTaiKhoan;
        this.tenChuTaiKhoan = tenChuTaiKhoan;
        this.soDu = 0.0;
    }
    
    // Constructor 3: Đầy đủ thông tin
    public TaiKhoanNganHang(String soTaiKhoan, String tenChuTaiKhoan, double soDu) {
        this.soTaiKhoan = soTaiKhoan;
        this.tenChuTaiKhoan = tenChuTaiKhoan;
        this.soDu = (soDu >= 0) ? soDu : 0.0;
    }
    
    // Getter/Setter
    public String getSoTaiKhoan() { return soTaiKhoan; }
    public String getTenChuTaiKhoan() { return tenChuTaiKhoan; }
    public double getSoDu() { return soDu; }
    
    public void setTenChuTaiKhoan(String ten) {
        if (ten != null && ten.length() > 0) {
            this.tenChuTaiKhoan = ten;
        }
    }
    
    // Phương thức gửi tiền
    public boolean guiTien(double soTien) {
        if (soTien > 0) {
            this.soDu += soTien;
            System.out.println("Gửi tiền thành công! Số dư mới: " + this.soDu);
            return true;
        }
        System.out.println("Số tiền gửi phải > 0!");
        return false;
    }
    
    // Phương thức rút tiền
    public boolean rutTien(double soTien) {
        if (soTien > 0 && soTien <= this.soDu) {
            this.soDu -= soTien;
            System.out.println("Rút tiền thành công! Số dư mới: " + this.soDu);
            return true;
        }
        System.out.println("Số tiền rút không hợp lệ!");
        return false;
    }
}

III. CÂU HỎI VÀ BÀI TẬP

Phần A: Câu hỏi lý thuyết (5 phút)

  1. Câu 1: Constructor khác phương thức thường ở những điểm nào?
  2. Câu 2: Tại sao cần sử dụng từ khóa this trong constructor?
  3. Câu 3: Tính đóng gói mang lại lợi ích gì cho việc phát triển phần mềm?
  4. Câu 4: Sự khác biệt giữa public, private, protected và default?
  5. Câu 5: Tại sao nên đặt thuộc tính là private và cung cấp getter/setter?

Phần B: Bài tập thực hành (25 phút)

Bài 1: Lớp Sản phẩm với Encapsulation

Tạo lớp SanPham với:

  • Thuộc tính private: maSP, tenSP, gia, soLuong
  • 2 constructor: mặc định và có tham số
  • Getter/Setter với validation:
    • Giá phải > 0
    • Số lượng phải ≥ 0
    • Mã SP không được rỗng
  • Phương thức tinhThanhTien(): tính giá * số lượng
  • Phương thức hienThiThongTin()

Bài 2: Lớp Nhân viên

Tạo lớp NhanVien với:

  • Thuộc tính private: maNV, hoTen, luongCoBan, heSoLuong
  • Constructor nhận đầy đủ thông tin
  • Getter/Setter
  • Phương thức tinhLuong(): luongCoBan * heSoLuong
  • Phương thức xepLoai():
    • Nếu lương ≥ 20,000,000: “Xuất sắc”
    • Nếu lương ≥ 15,000,000: “Tốt”
    • Còn lại: “Khá”

Bài 3: Lớp Điểm trong mặt phẳng Oxy

Tạo lớp Diem với:

  • Thuộc tính private: x, y (tọa độ)
  • Constructor mặc định tạo điểm (0,0)
  • Constructor có tham số
  • Phương thức:
    • tinhKhoangCach(Diem d): tính khoảng cách giữa 2 điểm
    • kiemTraTrungNhau(Diem d): kiểm tra 2 điểm có trùng nhau không
    • doiXungQuaGoc(): trả về điểm đối xứng qua gốc tọa độ

Code mẫu gợi ý:

public class Diem {
    private double x;
    private double y;
    
    public Diem(double x, double y) {
        this.x = x;
        this.y = y;
    }
    
    public double tinhKhoangCach(Diem d) {
        return Math.sqrt(Math.pow(this.x - d.x, 2) + 
                         Math.pow(this.y - d.y, 2));
    }
}

Phần C: Thử thách nâng cao (15 phút)

Bài 4: Hệ thống quản lý thư viện (phần 1)

Tạo lớp Sach cho hệ thống thư viện:

  • Thuộc tính: maSach, tenSach, tacGia, namXuatBan, trangThai (đang mượn/ có sẵn)
  • Constructor với validation:
    • Năm xuất bản phải > 1900 và ≤ năm hiện tại
    • Trạng thái mặc định là “có sẵn”
  • Phương thức:
    • muonSach(): chỉ cho mượn nếu sách có sẵn
    • traSach(): trả sách về trạng thái có sẵn
    • kiemTraSachCu(): trả về true nếu sách xuất bản trước 2000

Bài 5: Quản lý tài khoản email

Tạo lớp EmailAccount với:

  • Thuộc tính: email, password, storageLimit (GB), usedStorage (GB)
  • Validation:
    • Email phải có định dạng hợp lệ (chứa @ và .)
    • Password ít nhất 8 ký tự
    • StorageLimit > 0
  • Phương thức:
    • changePassword(String oldPass, String newPass): đổi mật khẩu
    • uploadFile(double sizeGB): tải file lên nếu còn đủ dung lượng
    • calculateStoragePercentage(): tính % dung lượng đã dùng

IV. TÓM TẮT BÀI HỌC

  • Constructor khởi tạo đối tượng một cách thông minh
  • Tính đóng gói bảo vệ dữ liệu với private + getter/setter
  • this tham chiếu đến đối tượng hiện tại
  • Access modifiers kiểm soát mức độ truy cập
  • Validation trong setter đảm bảo tính hợp lệ của dữ liệu

Comments are closed.