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

🎨 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ểmAbstract ClassInterface
Keywordabstract classinterface
MethodAbstract + ConcreteAbstract (+ default Java 8+)
BiếnMọi loạipublic static final
ConstructorKhông
Đa kế thừaKhông (extends 1)Có (implements nhiều)
Access modifierMọi loạipublic
// 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

  1. Abstract = Bản nháp: Chưa hoàn chỉnh, cần hoàn thiện
  2. Template method: Định nghĩa khung, chi tiết cho subclass
  3. Code reuse: Dùng abstract class khi có code chung
  4. Is-a relationship: Chỉ dùng khi quan hệ "là một"
  5. Interface vs Abstract: Interface cho hành vi, abstract cho bản thiết kế
  6. 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ể new abstract 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:

👉 Polymorphism - Đa Hình


💡 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!