Chuyển tới nội dung chính

🔒 Đóng Gói - Encapsulation

Chào mừng đến với bài học về Encapsulation - bảo mật công thức trong Café!

🎯 Món Ăn Hôm Nay

Tưởng tượng bạn có công thức bí mật:

  • Công thức riêng tư → private
  • Chỉ xem qua menu → public getter
  • Đổi giá có kiểm tra → public setter
  • Bảo vệ dữ liệu → Encapsulation

Đó chính là Encapsulation - đóng gói và bảo vệ!

🔒 Encapsulation Là Gì?

Encapsulation = Đóng gói (ẩn chi tiết, chỉ cung cấp interface)

public class DoUong {
// Private - Ẩn chi tiết
private String ten;
private int gia;
private String congThucBiMat;

// Public constructor
public DoUong(String ten, int gia) {
this.ten = ten;
this.gia = gia;
this.congThucBiMat = "Bí mật";
}

// Public getter - Đọc được
public String getTen() {
return ten;
}

public int getGia() {
return gia;
}

// Public setter - Ghi được (có validation)
public void setGia(int gia) {
if (gia > 0) {
this.gia = gia;
} else {
System.out.println("❌ Giá phải > 0");
}
}

// Không có setter cho congThucBiMat → Chỉ đọc
}

Ẩn Dụ Café:

  • private = Công thức bí mật (chỉ mình biết)
  • public = Menu công khai (ai cũng thấy)
  • Getter = Cho xem giá (đọc)
  • Setter = Đổi giá (ghi, có kiểm tra)
  • Validation = Kiểm tra hợp lệ

📋 Access Modifiers

ModifierClassPackageSubclassWorld
public
protected
default
private
public class Example {
public int a; // Mọi nơi truy cập được
protected int b; // Package + subclass
int c; // Chỉ trong package (default)
private int d; // Chỉ trong class
}

👨‍🍳 Câu Chuyện Trong Quán

Tình huống 1: Getter & Setter Cơ Bản

public class SanPham {
private String ten;
private int gia;
private int soLuong;

public SanPham(String ten, int gia, int soLuong) {
this.ten = ten;
setGia(gia); // Dùng setter để validate
setSoLuong(soLuong);
}

// Getter
public String getTen() {
return ten;
}

public int getGia() {
return gia;
}

public int getSoLuong() {
return soLuong;
}

// Setter với validation
public void setGia(int gia) {
if (gia >= 10000 && gia <= 1000000) {
this.gia = gia;
} else {
System.out.println("❌ Giá phải từ 10,000 đến 1,000,000");
this.gia = 10000; // Giá mặc định
}
}

public void setSoLuong(int soLuong) {
if (soLuong >= 0) {
this.soLuong = soLuong;
} else {
System.out.println("❌ Số lượng không thể âm");
this.soLuong = 0;
}
}

public void hienThi() {
System.out.printf("%-15s: %,7d VND (Kho: %d)%n", ten, gia, soLuong);
}
}

// Sử dụng
public class TestEncapsulation {
public static void main(String[] args) {
SanPham sp = new SanPham("Latte", 50000, 10);

// Đọc qua getter
System.out.println("Tên: " + sp.getTen());
System.out.println("Giá: " + sp.getGia());

// Ghi qua setter (có validation)
sp.setGia(60000); // ✅ OK
sp.setGia(-5000); // ❌ Không hợp lệ

sp.hienThi();
}
}

Output:

Tên: Latte
Giá: 50000
❌ Giá phải từ 10,000 đến 1,000,000
Latte : 60,000 VND (Kho: 10)

📝 Công Thức Nấu (Code Examples)

Ví Dụ 1: Read-Only Property

public class DonHang {
private String maDon;
private int tongTien;
private final long thoiGianTao; // Chỉ đọc

public DonHang(String maDon, int tongTien) {
this.maDon = maDon;
this.tongTien = tongTien;
this.thoiGianTao = System.currentTimeMillis();
}

// Getter cho maDon
public String getMaDon() {
return maDon;
}

// Getter cho tongTien
public int getTongTien() {
return tongTien;
}

// Chỉ getter, không setter → Read-only
public long getThoiGianTao() {
return thoiGianTao;
}

// Setter cho tongTien
public void setTongTien(int tongTien) {
if (tongTien > 0) {
this.tongTien = tongTien;
}
}

// Không có setter cho maDon → Không đổi được
// Không có setter cho thoiGianTao → final
}

Ví Dụ 2: Computed Property

public class NhanVien {
private String ten;
private int luongCoBan;
private int phuCap;
private int soGioLamThem;

public NhanVien(String ten, int luongCoBan, int phuCap) {
this.ten = ten;
this.luongCoBan = luongCoBan;
this.phuCap = phuCap;
this.soGioLamThem = 0;
}

// Getters
public String getTen() {
return ten;
}

public int getLuongCoBan() {
return luongCoBan;
}

public void setSoGioLamThem(int gio) {
if (gio >= 0 && gio <= 80) {
this.soGioLamThem = gio;
}
}

// Computed property - tính toán từ các thuộc tính khác
public int getLuongThucTe() {
return luongCoBan + phuCap + (soGioLamThem * 50000);
}

public void hienThi() {
System.out.printf("👤 %s:%n", ten);
System.out.printf(" Lương cơ bản: %,d VND%n", luongCoBan);
System.out.printf(" Phụ cấp: %,d VND%n", phuCap);
System.out.printf(" Làm thêm: %d giờ%n", soGioLamThem);
System.out.printf(" 💵 Tổng: %,d VND%n", getLuongThucTe());
}
}

Ví Dụ 3: Validation Trong Setter

public class TaiKhoan {
private String tenDangNhap;
private String matKhau;
private String email;
private int soDu;

public TaiKhoan(String tenDangNhap) {
this.tenDangNhap = tenDangNhap;
this.soDu = 0;
}

// Setter với validation phức tạp
public void setMatKhau(String matKhau) {
if (matKhau == null || matKhau.length() < 6) {
System.out.println("❌ Mật khẩu phải ít nhất 6 ký tự");
return;
}

if (!matKhau.matches(".*[A-Z].*")) {
System.out.println("❌ Mật khẩu phải có chữ hoa");
return;
}

if (!matKhau.matches(".*[0-9].*")) {
System.out.println("❌ Mật khẩu phải có số");
return;
}

this.matKhau = matKhau;
System.out.println("✅ Đặt mật khẩu thành công");
}

public void setEmail(String email) {
if (email != null && email.contains("@")) {
this.email = email;
System.out.println("✅ Email hợp lệ");
} else {
System.out.println("❌ Email không hợp lệ");
}
}

public void napTien(int soTien) {
if (soTien > 0) {
soDu += soTien;
System.out.printf("✅ Nạp %,d VND. Số dư: %,d VND%n", soTien, soDu);
}
}

public void rutTien(int soTien) {
if (soTien > soDu) {
System.out.println("❌ Không đủ tiền");
} else if (soTien <= 0) {
System.out.println("❌ Số tiền không hợp lệ");
} else {
soDu -= soTien;
System.out.printf("✅ Rút %,d VND. Còn lại: %,d VND%n", soTien, soDu);
}
}

public int getSoDu() {
return soDu;
}
}

// Test
public class TestValidation {
public static void main(String[] args) {
TaiKhoan tk = new TaiKhoan("minh123");

tk.setMatKhau("abc"); // ❌ Quá ngắn
tk.setMatKhau("abcdef"); // ❌ Thiếu chữ hoa
tk.setMatKhau("Abcdef"); // ❌ Thiếu số
tk.setMatKhau("Abc123"); // ✅ OK

tk.setEmail("invalid"); // ❌
tk.setEmail("[email protected]"); // ✅

tk.napTien(100000);
tk.rutTien(50000);
tk.rutTien(60000); // ❌ Không đủ
}
}

Ví Dụ 4: Lazy Initialization

public class CauHinh {
private static CauHinh instance;
private String duongDanDatabase;
private int soKetNoiToiDa;

// Private constructor
private CauHinh() {
this.duongDanDatabase = "localhost:3306/cafe";
this.soKetNoiToiDa = 10;
System.out.println("🔧 Đã khởi tạo cấu hình");
}

// Lazy initialization
public static CauHinh getInstance() {
if (instance == null) {
instance = new CauHinh();
}
return instance;
}

public String getDuongDanDatabase() {
return duongDanDatabase;
}

public void setDuongDanDatabase(String duongDan) {
this.duongDanDatabase = duongDan;
}

public int getSoKetNoiToiDa() {
return soKetNoiToiDa;
}
}

// Test
public class TestLazy {
public static void main(String[] args) {
System.out.println("Chưa tạo instance...");

CauHinh c1 = CauHinh.getInstance(); // Tạo lần đầu
CauHinh c2 = CauHinh.getInstance(); // Dùng lại

System.out.println("c1 == c2? " + (c1 == c2)); // true
}
}

Ví Dụ 5: Immutable Class

public final class ThongTinDonHang {
private final String maDon;
private final String tenKhach;
private final int tongTien;
private final long ngayTao;

public ThongTinDonHang(String maDon, String tenKhach, int tongTien) {
this.maDon = maDon;
this.tenKhach = tenKhach;
this.tongTien = tongTien;
this.ngayTao = System.currentTimeMillis();
}

// Chỉ getter, không setter
public String getMaDon() {
return maDon;
}

public String getTenKhach() {
return tenKhach;
}

public int getTongTien() {
return tongTien;
}

public long getNgayTao() {
return ngayTao;
}

@Override
public String toString() {
return String.format("Đơn %s | %s | %,d VND", maDon, tenKhach, tongTien);
}
}

// Không thể thay đổi sau khi tạo

🔥 Thực Hành Trong Quán

Bài Tập 1: Class SanPham Hoàn Chỉnh

public class SanPham {
private String ma;
private String ten;
private int gia;
private int soLuongTonKho;
private String loai;

public SanPham(String ma, String ten, int gia, int soLuongTonKho, String loai) {
this.ma = ma;
this.ten = ten;
setGia(gia);
setSoLuongTonKho(soLuongTonKho);
this.loai = loai;
}

// Getters
public String getMa() { return ma; }
public String getTen() { return ten; }
public int getGia() { return gia; }
public int getSoLuongTonKho() { return soLuongTonKho; }
public String getLoai() { return loai; }

// Setters với validation
public void setTen(String ten) {
if (ten != null && !ten.isEmpty()) {
this.ten = ten;
}
}

public void setGia(int gia) {
if (gia > 0) {
this.gia = gia;
} else {
this.gia = 10000; // Giá tối thiểu
}
}

public void setSoLuongTonKho(int soLuong) {
if (soLuong >= 0) {
this.soLuongTonKho = soLuong;
} else {
this.soLuongTonKho = 0;
}
}

// Business methods
public boolean conHang() {
return soLuongTonKho > 0;
}

public void themKho(int soLuong) {
if (soLuong > 0) {
soLuongTonKho += soLuong;
System.out.printf("✅ Thêm %d %s vào kho%n", soLuong, ten);
}
}

public boolean banHang(int soLuong) {
if (soLuong > soLuongTonKho) {
System.out.println("❌ Không đủ hàng");
return false;
}
soLuongTonKho -= soLuong;
return true;
}

public void hienThi() {
System.out.printf("[%s] %-15s: %,7d VND (Kho: %d)%n",
ma, ten, gia, soLuongTonKho);
}
}

⚠️ Lỗi Thường Gặp

Lỗi 1: Public Biến Trực Tiếp

// ❌ SAI: public biến
public class SanPham {
public int gia; // Ai cũng sửa được
}

SanPham sp = new SanPham();
sp.gia = -1000; // ❌ Không kiểm tra!

// ✅ ĐÚNG: private + getter/setter
public class SanPham {
private int gia;

public void setGia(int gia) {
if (gia > 0) {
this.gia = gia;
}
}
}

Lỗi 2: Setter Trả Về Mutable Object

// ❌ SAI: Trả về reference trực tiếp
public class GioHang {
private ArrayList<String> items = new ArrayList<>();

public ArrayList<String> getItems() {
return items; // ❌ Có thể sửa từ bên ngoài!
}
}

GioHang gh = new GioHang();
gh.getItems().clear(); // ❌ Xóa hết!

// ✅ ĐÚNG: Trả về copy
public ArrayList<String> getItems() {
return new ArrayList<>(items); // ✅ Copy
}

Lỗi 3: Validation Không Đủ

// ❌ SAI: Validation thiếu
public void setTuoi(int tuoi) {
this.tuoi = tuoi; // Không kiểm tra
}

// ✅ ĐÚNG: Validation đầy đủ
public void setTuoi(int tuoi) {
if (tuoi >= 18 && tuoi <= 100) {
this.tuoi = tuoi;
} else {
throw new IllegalArgumentException("Tuổi không hợp lệ");
}
}

💡 Bí Quyết Của Barista

  1. private mặc định: Luôn dùng private cho biến
  2. public getter/setter: Cung cấp interface rõ ràng
  3. Validation trong setter: Kiểm tra dữ liệu hợp lệ
  4. Read-only: Chỉ getter, không setter
  5. Computed property: Getter tính toán từ biến khác
  6. Immutable: final class + final fields

🎓 Bạn Đã Học Được

  • Encapsulation = Đóng gói và bảo vệ
  • private = Ẩn chi tiết
  • public = Interface công khai
  • Getter/Setter = Đọc/ghi có kiểm soát
  • ✅ Validation trong setter
  • ✅ Read-only property (chỉ getter)
  • ✅ Computed property (tính toán)
  • ✅ Immutable class (không đổi được)

☕ Món Tiếp Theo

Đã biết encapsulation! Giờ học về exception handling:

👉 Try-Catch - Xử Lý Ngoại Lệ


💡 Lời Khuyên Cuối: Encapsulation như két bảo mật - công thức bí mật giữ kín, chỉ cho xem qua cửa sổ!