🔒 Đó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
| Modifier | Class | Package | Subclass | World |
|---|---|---|---|---|
| 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
- private mặc định: Luôn dùng private cho biến
- public getter/setter: Cung cấp interface rõ ràng
- Validation trong setter: Kiểm tra dữ liệu hợp lệ
- Read-only: Chỉ getter, không setter
- Computed property: Getter tính toán từ biến khác
- 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:
💡 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ổ!