🎨 Lớp Trừu Tượng - Abstract Classes
Chào mừng đến với bài học về Abstract Classes - bản thiết kế chung cho Café!
🎯 Món Ăn Hôm Nay
Tưởng tượng bạn có bản thiết kế chung cho đồ uống:
- Bản thiết kế chung → Abstract class DoUong
- Phương thức chung → Các method dùng chung
- Phương thức riêng → Abstract method cho từng loại
- Không thể tạo trực tiếp → Phải extends
Đó chính là Abstract Class - class nửa hoàn thiện!
🎨 Abstract Class Là Gì?
Abstract Class = Lớp trừu tượng (class chưa hoàn chỉnh, cần extends để dùng)
// Abstract class - không thể new trực tiếp
public abstract class DoUong {
protected String ten;
protected int gia;
// Constructor
public DoUong(String ten, int gia) {
this.ten = ten;
this.gia = gia;
}
// Abstract method - không có body
public abstract void pha();
// Concrete method - có body
public void hienThi() {
System.out.printf("☕ %s: %,d VND%n", ten, gia);
}
}
// Class con extends và implement abstract method
public class Latte extends DoUong {
public Latte(int gia) {
super("Latte", gia);
}
@Override
public void pha() {
System.out.println("☕ Pha Latte với sữa và foam");
}
}
☕ Ẩn Dụ Café:
- Abstract class = Bản thiết kế chưa hoàn chỉnh
- Abstract method = Bước cần tùy chỉnh
- Concrete method = Bước chung cho tất cả
- extends = Hoàn thiện bản thiết kế
- Cannot instantiate = Không thể dùng bản nháp
📋 Đặc Điểm Abstract Class
| Đặc điểm | Abstract Class | Interface |
|---|---|---|
| Keyword | abstract class | interface |
| Method | Abstract + Concrete | Abstract (+ default Java 8+) |
| Biến | Mọi loại | public static final |
| Constructor | Có | Không |
| Đa kế thừa | Không (extends 1) | Có (implements nhiều) |
| Access modifier | Mọi loại | public |
// Abstract class
public abstract class NhanVien {
private String ten; // Biến instance
public NhanVien(String ten) { // Constructor
this.ten = ten;
}
public abstract void lamViec(); // Abstract method
public void diLam() { // Concrete method
System.out.println(ten + " đi làm");
}
}
// Interface
public interface PhucVu {
int GIO_MO_CUA = 7; // public static final
void chaoKhach(); // abstract method
default void tamBiet() { // default method
System.out.println("Hẹn gặp lại!");
}
}
👨🍳 Câu Chuyện Trong Quán
Tình huống 1: Abstract Class Cơ Bản
public abstract class DoUong {
protected String ten;
protected int gia;
public DoUong(String ten, int gia) {
this.ten = ten;
this.gia = gia;
}
// Abstract method - phải override
public abstract void pha();
// Concrete method - dùng chung
public void phucVu() {
System.out.println("📝 Nhận đơn: " + ten);
pha(); // Gọi abstract method
System.out.println("✅ Hoàn thành: " + ten);
}
public void hienThi() {
System.out.printf("☕ %s: %,d VND%n", ten, gia);
}
}
public class CaPhe extends DoUong {
public CaPhe(String ten, int gia) {
super(ten, gia);
}
@Override
public void pha() {
System.out.println(" ☕ Đang pha cà phê " + ten);
}
}
public class TraSua extends DoUong {
public TraSua(String ten, int gia) {
super(ten, gia);
}
@Override
public void pha() {
System.out.println(" 🧋 Đang pha trà sữa " + ten);
}
}
// Sử dụng
public class TestAbstract {
public static void main(String[] args) {
// DoUong d = new DoUong("Test", 0); // ❌ Lỗi! Không thể new abstract
DoUong latte = new CaPhe("Latte", 50000);
DoUong traSua = new TraSua("Trân châu", 45000);
latte.phucVu();
System.out.println();
traSua.phucVu();
}
}
Output:
📝 Nhận đơn: Latte
☕ Đang pha cà phê Latte
✅ Hoàn thành: Latte
📝 Nhận đơn: Trân châu
🧋 Đang pha trà sữa Trân châu
✅ Hoàn thành: Trân châu
📝 Công Thức Nấu (Code Examples)
Ví Dụ 1: Template Method Pattern
public abstract class QuaTrinhPhaChe {
// Template method - final để không override
public final void phaMon() {
System.out.println("🔥 BẮT ĐẦU PHA:\n");
chuanBiNguyenLieu();
pha();
trangTri();
phucVu();
System.out.println("\n✅ HOÀN THÀNH!");
}
// Concrete methods
private void chuanBiNguyenLieu() {
System.out.println("1. Chuẩn bị nguyên liệu");
}
private void phucVu() {
System.out.println("4. Phục vụ khách");
}
// Abstract methods - override bởi subclass
protected abstract void pha();
protected abstract void trangTri();
}
public class PhaLatte extends QuaTrinhPhaChe {
@Override
protected void pha() {
System.out.println("2. Pha Espresso + sữa nóng");
}
@Override
protected void trangTri() {
System.out.println("3. Tạo lớp foam + latte art");
}
}
public class PhaMocha extends QuaTrinhPhaChe {
@Override
protected void pha() {
System.out.println("2. Pha Espresso + chocolate + sữa");
}
@Override
protected void trangTri() {
System.out.println("3. Rắc bột chocolate lên trên");
}
}
// Sử dụng
public class TestTemplate {
public static void main(String[] args) {
QuaTrinhPhaChe latte = new PhaLatte();
latte.phaMon();
System.out.println("\n" + "=".repeat(40) + "\n");
QuaTrinhPhaChe mocha = new PhaMocha();
mocha.phaMon();
}
}
Ví Dụ 2: Abstract Class Với Constructor
public abstract class NhanVien {
protected String ten;
protected int luongCoBan;
protected static int soNhanVien = 0;
public NhanVien(String ten, int luongCoBan) {
this.ten = ten;
this.luongCoBan = luongCoBan;
soNhanVien++;
System.out.println("✅ Đã tạo nhân viên: " + ten);
}
// Abstract method
public abstract int tinhLuong();
// Concrete method
public void hienThi() {
System.out.printf("👤 %s: %,d VND%n", ten, tinhLuong());
}
public static int laySoNhanVien() {
return soNhanVien;
}
}
public class BaristaFullTime extends NhanVien {
private int phuCap;
public BaristaFullTime(String ten, int luongCoBan, int phuCap) {
super(ten, luongCoBan);
this.phuCap = phuCap;
}
@Override
public int tinhLuong() {
return luongCoBan + phuCap;
}
}
public class BaristaPartTime extends NhanVien {
private int soGio;
private static final int GIA_GIO = 50000;
public BaristaPartTime(String ten, int soGio) {
super(ten, 0); // Không lương cơ bản
this.soGio = soGio;
}
@Override
public int tinhLuong() {
return soGio * GIA_GIO;
}
}
// Test
public class TestConstructor {
public static void main(String[] args) {
NhanVien nv1 = new BaristaFullTime("Minh", 7000000, 1000000);
NhanVien nv2 = new BaristaPartTime("Lan", 80);
System.out.println("\n💼 DANH SÁCH NHÂN VIÊN:\n");
nv1.hienThi();
nv2.hienThi();
System.out.println("\n📊 Tổng số: " + NhanVien.laySoNhanVien());
}
}
Ví Dụ 3: Kết Hợp Abstract Class & Interface
// Interface
public interface ThanhToan {
void thanhToan(int soTien);
}
// Abstract class implements interface
public abstract class DonHang implements ThanhToan {
protected String maDon;
protected int tongTien;
public DonHang(String maDon, int tongTien) {
this.maDon = maDon;
this.tongTien = tongTien;
}
// Abstract method
public abstract int tinhGiamGia();
// Concrete method
public void inHoaDon() {
int giamGia = tinhGiamGia();
int thanhToan = tongTien - giamGia;
System.out.println("🧾 MÃ ĐƠN: " + maDon);
System.out.printf(" Tổng: %,d VND%n", tongTien);
System.out.printf(" Giảm: %,d VND%n", giamGia);
System.out.printf(" Thanh toán: %,d VND%n", thanhToan);
}
// Implement interface method
@Override
public void thanhToan(int soTien) {
System.out.printf("💰 Đã thanh toán: %,d VND%n", soTien);
}
}
public class DonHangThuong extends DonHang {
public DonHangThuong(String maDon, int tongTien) {
super(maDon, tongTien);
}
@Override
public int tinhGiamGia() {
return 0; // Không giảm
}
}
public class DonHangVIP extends DonHang {
public DonHangVIP(String maDon, int tongTien) {
super(maDon, tongTien);
}
@Override
public int tinhGiamGia() {
return tongTien * 20 / 100; // Giảm 20%
}
}
Ví Dụ 4: Abstract Class Với Generic
public abstract class KhoLuuTru<T> {
protected String ten;
public KhoLuuTru(String ten) {
this.ten = ten;
}
// Abstract methods
public abstract void them(T item);
public abstract T lay(int index);
public abstract int kichThuoc();
// Concrete method
public void hienThi() {
System.out.println("📦 Kho: " + ten);
System.out.println(" Số lượng: " + kichThuoc());
}
}
public class KhoDoUong extends KhoLuuTru<String> {
private java.util.ArrayList<String> danhSach = new java.util.ArrayList<>();
public KhoDoUong(String ten) {
super(ten);
}
@Override
public void them(String doUong) {
danhSach.add(doUong);
}
@Override
public String lay(int index) {
return danhSach.get(index);
}
@Override
public int kichThuoc() {
return danhSach.size();
}
}
// Test
public class TestGeneric {
public static void main(String[] args) {
KhoDoUong kho = new KhoDoUong("Kho đồ uống");
kho.them("Latte");
kho.them("Mocha");
kho.them("Espresso");
kho.hienThi();
System.out.println("\n☕ Danh sách:");
for (int i = 0; i < kho.kichThuoc(); i++) {
System.out.println(" - " + kho.lay(i));
}
}
}
Ví Dụ 5: So Sánh Abstract Class vs Interface
// Trường hợp 1: Dùng Abstract Class
// Khi có code dùng chung + trạng thái chung
public abstract class SanPham {
protected String ma;
protected String ten;
protected int gia;
public SanPham(String ma, String ten, int gia) {
this.ma = ma;
this.ten = ten;
this.gia = gia;
}
public abstract int tinhGiamGia();
public int tinhThanhToan() { // Code chung
return gia - tinhGiamGia();
}
}
// Trường hợp 2: Dùng Interface
// Khi chỉ định nghĩa hành vi, không có trạng thái
public interface XuatHoaDon {
void xuatHoaDon();
}
// Kết hợp cả hai
public class DoUong extends SanPham implements XuatHoaDon {
public DoUong(String ma, String ten, int gia) {
super(ma, ten, gia);
}
@Override
public int tinhGiamGia() {
return gia * 10 / 100;
}
@Override
public void xuatHoaDon() {
System.out.printf("🧾 %s - %s: %,d VND%n", ma, ten, tinhThanhToan());
}
}
🔥 Thực Hành Trong Quán
Bài Tập 1: Hệ Thống Hình Học
public abstract class HinhHoc {
protected String ten;
protected String mau;
public HinhHoc(String ten, String mau) {
this.ten = ten;
this.mau = mau;
}
// Abstract methods
public abstract double tinhDienTich();
public abstract double tinhChuVi();
// Concrete method
public void hienThi() {
System.out.printf("🔷 %s (%s):%n", ten, mau);
System.out.printf(" Diện tích: %.2f m²%n", tinhDienTich());
System.out.printf(" Chu vi: %.2f m%n", tinhChuVi());
}
}
public class HinhVuong extends HinhHoc {
private double canh;
public HinhVuong(double canh, String mau) {
super("Hình vuông", mau);
this.canh = canh;
}
@Override
public double tinhDienTich() {
return canh * canh;
}
@Override
public double tinhChuVi() {
return canh * 4;
}
}
public class HinhTron extends HinhHoc {
private double banKinh;
public HinhTron(double banKinh, String mau) {
super("Hình tròn", mau);
this.banKinh = banKinh;
}
@Override
public double tinhDienTich() {
return Math.PI * banKinh * banKinh;
}
@Override
public double tinhChuVi() {
return 2 * Math.PI * banKinh;
}
}
Bài Tập 2: Factory Pattern
public abstract class DoUongFactory {
public DoUong taoDoUong(String loai) {
DoUong doUong = taoDoUongCuThe(loai);
if (doUong != null) {
doUong.chuanBi();
}
return doUong;
}
// Abstract factory method
protected abstract DoUong taoDoUongCuThe(String loai);
}
public class DoUong {
protected String ten;
public DoUong(String ten) {
this.ten = ten;
}
public void chuanBi() {
System.out.println("✅ Đã chuẩn bị: " + ten);
}
}
public class CaPheFactory extends DoUongFactory {
@Override
protected DoUong taoDoUongCuThe(String loai) {
if (loai.equals("Latte")) {
return new DoUong("Latte");
} else if (loai.equals("Mocha")) {
return new DoUong("Mocha");
}
return null;
}
}
⚠️ Lỗi Thường Gặp
Lỗi 1: Không Implement Abstract Method
public abstract class DoUong {
public abstract void pha();
}
// ❌ SAI: Không implement pha()
public class Latte extends DoUong {
// Thiếu @Override pha()
}
// ✅ ĐÚNG: Implement đầy đủ
public class Latte extends DoUong {
@Override
public void pha() {
System.out.println("Pha Latte");
}
}
Lỗi 2: Tạo Object Abstract Class
public abstract class DoUong {
public abstract void pha();
}
// ❌ SAI: Không thể new abstract class
DoUong d = new DoUong(); // ❌ Lỗi!
// ✅ ĐÚNG: new class con
DoUong d = new Latte(); // ✅ OK
Lỗi 3: Abstract Method Trong Non-Abstract Class
// ❌ SAI: Abstract method trong class thường
public class DoUong {
public abstract void pha(); // ❌ Lỗi!
}
// ✅ ĐÚNG: Class phải abstract
public abstract class DoUong {
public abstract void pha(); // ✅ OK
}
Lỗi 4: Abstract Method Có Body
// ❌ SAI: Abstract method không có body
public abstract class DoUong {
public abstract void pha() { // ❌ Lỗi!
System.out.println("Pha");
}
}
// ✅ ĐÚNG: Không có body hoặc bỏ abstract
public abstract class DoUong {
public abstract void pha(); // ✅ OK - abstract
public void hienThi() { // ✅ OK - concrete
System.out.println("Hiển thị");
}
}
💡 Bí Quyết Của Barista
- Abstract = Bản nháp: Chưa hoàn chỉnh, cần hoàn thiện
- Template method: Định nghĩa khung, chi tiết cho subclass
- Code reuse: Dùng abstract class khi có code chung
- Is-a relationship: Chỉ dùng khi quan hệ "là một"
- Interface vs Abstract: Interface cho hành vi, abstract cho bản thiết kế
- Constructor: Abstract class có constructor cho subclass
🎓 Bạn Đã Học Được
- ✅ Abstract class = Lớp trừu tượng
- ✅ Keyword:
abstract - ✅ Abstract method = Method không body
- ✅ Concrete method = Method có body
- ✅ Không thể
newabstract class - ✅ Subclass phải implement abstract methods
- ✅ Có constructor, biến instance
- ✅ So sánh: Abstract class vs Interface
☕ Món Tiếp Theo
Đã biết abstract class! Giờ học về polymorphism:
💡 Lời Khuyên Cuối: Abstract class như bản thiết kế nhà - khung sườn có sẵn, chi tiết tùy chỉnh!