Skip to main content

🎭 Đa Hình - Polymorphism

Chào mừng đến với bài học về Polymorphism - một giao diện nhiều cách thực hiện trong Café!

🎯 Món Ăn Hôm Nay

Tưởng tượng bạn nói "Pha cà phê":

  • Barista A pha Espresso
  • Barista B pha Latte
  • Barista C pha Mocha
  • Cùng lệnh, khác cách làm

Đó chính là Polymorphism - đa hình!

🎭 Polymorphism Là Gì?

Polymorphism = Đa hình (một interface nhiều implementation)

// Cha
public class DoUong {
public void pha() {
System.out.println("☕ Pha đồ uống");
}
}

// Con 1
public class Latte extends DoUong {
@Override
public void pha() {
System.out.println("☕ Pha Latte");
}
}

// Con 2
public class Mocha extends DoUong {
@Override
public void pha() {
System.out.println("🍫 Pha Mocha");
}
}

// Polymorphism
DoUong d1 = new Latte(); // Cha trỏ con
DoUong d2 = new Mocha();

d1.pha(); // Gọi Latte.pha()
d2.pha(); // Gọi Mocha.pha()

Ẩn Dụ Café:

  • Polymorphism = Một tên gọi, nhiều cách làm
  • Upcasting = Gọi chung là "đồ uống"
  • Dynamic binding = Biết cách pha khi runtime
  • Override = Mỗi loại pha khác nhau

📋 Các Loại Polymorphism

1. Compile-time Polymorphism (Static)

  • Method Overloading
  • Quyết định lúc compile
public class TinhToan {
public int cong(int a, int b) {
return a + b;
}

public double cong(double a, double b) {
return a + b;
}
}

2. Runtime Polymorphism (Dynamic)

  • Method Overriding
  • Quyết định lúc runtime
DoUong d = new Latte();  // Runtime xác định gọi Latte.pha()
d.pha();

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

Tình huống 1: Polymorphism Cơ Bản

public class NhanVien {
protected String ten;

public NhanVien(String ten) {
this.ten = ten;
}

public void lamViec() {
System.out.println("👤 " + ten + " đang làm việc");
}
}

public class Barista extends NhanVien {
public Barista(String ten) {
super(ten);
}

@Override
public void lamViec() {
System.out.println("☕ " + ten + " đang pha cà phê");
}
}

public class ThuNgan extends NhanVien {
public ThuNgan(String ten) {
super(ten);
}

@Override
public void lamViec() {
System.out.println("💰 " + ten + " đang thu ngân");
}
}

// Sử dụng polymorphism
public class TestPolymorphism {
public static void phanCongCongViec(NhanVien nv) {
nv.lamViec(); // Gọi đúng phiên bản runtime
}

public static void main(String[] args) {
NhanVien[] danhSach = {
new Barista("Minh"),
new ThuNgan("Lan"),
new Barista("Hoa")
};

System.out.println("📋 PHÂN CÔNG CÔNG VIỆC:\n");

for (NhanVien nv : danhSach) {
phanCongCongViec(nv);
}
}
}

Output:

📋 PHÂN CÔNG CÔNG VIỆC:

☕ Minh đang pha cà phê
💰 Lan đang thu ngân
☕ Hoa đang pha cà phê

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

Ví Dụ 1: Upcasting & Downcasting

public class DoUong {
protected String ten;

public DoUong(String ten) {
this.ten = ten;
}

public void hienThi() {
System.out.println("☕ " + ten);
}
}

public class Latte extends DoUong {
private String loaiSua;

public Latte(String loaiSua) {
super("Latte");
this.loaiSua = loaiSua;
}

public void themSua() {
System.out.println("🥛 Thêm sữa: " + loaiSua);
}
}

public class TestCasting {
public static void main(String[] args) {
// Upcasting (tự động)
DoUong d = new Latte("Sữa tươi"); // ✅ OK
d.hienThi(); // ✅ OK
// d.themSua(); // ❌ Lỗi! DoUong không có themSua()

// Downcasting (thủ công)
if (d instanceof Latte) {
Latte latte = (Latte) d; // Cast về Latte
latte.themSua(); // ✅ OK
}
}
}

Output:

☕ Latte
🥛 Thêm sữa: Sữa tươi

Ví Dụ 2: instanceof Operator

public class DoUong {
protected String ten;
public DoUong(String ten) { this.ten = ten; }
}

public class CaPhe extends DoUong {
public CaPhe(String ten) { super(ten); }
}

public class TraSua extends DoUong {
public TraSua(String ten) { super(ten); }
}

public class TestInstanceOf {
public static void xuLy(DoUong doUong) {
System.out.print("Đang xử lý: " + doUong.ten + " → ");

if (doUong instanceof CaPhe) {
System.out.println("Dùng máy pha cà phê");
} else if (doUong instanceof TraSua) {
System.out.println("Dùng shaker");
} else {
System.out.println("Dùng dụng cụ chung");
}
}

public static void main(String[] args) {
DoUong[] menu = {
new CaPhe("Latte"),
new TraSua("Trân châu"),
new DoUong("Nước ép")
};

for (DoUong d : menu) {
xuLy(d);
}
}
}

Output:

Đang xử lý: Latte → Dùng máy pha cà phê
Đang xử lý: Trân châu → Dùng shaker
Đang xử lý: Nước ép → Dùng dụng cụ chung

Ví Dụ 3: Polymorphism Với Interface

public interface ThanhToan {
void thanhToan(int soTien);
}

public class TienMat implements ThanhToan {
@Override
public void thanhToan(int soTien) {
System.out.printf("💵 Thanh toán tiền mặt: %,d VND%n", soTien);
}
}

public class TheNganHang implements ThanhToan {
@Override
public void thanhToan(int soTien) {
System.out.printf("💳 Thanh toán bằng thẻ: %,d VND%n", soTien);
}
}

public class ViDienTu implements ThanhToan {
@Override
public void thanhToan(int soTien) {
System.out.printf("📱 Thanh toán ví điện tử: %,d VND%n", soTien);
}
}

// Polymorphism với interface
public class TestInterfacePolymorphism {
public static void xuLyThanhToan(ThanhToan phuongThuc, int soTien) {
phuongThuc.thanhToan(soTien); // Polymorphic call
}

public static void main(String[] args) {
ThanhToan[] phuongThuc = {
new TienMat(),
new TheNganHang(),
new ViDienTu()
};

int soTien = 150000;

System.out.println("💰 XỬ LÝ THANH TOÁN:\n");

for (ThanhToan pt : phuongThuc) {
xuLyThanhToan(pt, soTien);
}
}
}

Ví Dụ 4: Method Overriding Chain

public class DoUong {
public void chuanBi() {
System.out.println("1. Chuẩn bị chung");
}
}

public class CaPhe extends DoUong {
@Override
public void chuanBi() {
super.chuanBi(); // Gọi cha
System.out.println("2. Chuẩn bị cà phê");
}
}

public class Latte extends CaPhe {
@Override
public void chuanBi() {
super.chuanBi(); // Gọi CaPhe
System.out.println("3. Chuẩn bị sữa cho Latte");
}
}

public class TestChain {
public static void main(String[] args) {
DoUong d = new Latte();
d.chuanBi(); // Gọi chuỗi từ cha đến con
}
}

Output:

1. Chuẩn bị chung
2. Chuẩn bị cà phê
3. Chuẩn bị sữa cho Latte

Ví Dụ 5: Array/Collection Polymorphism

public abstract class SanPham {
protected String ten;
protected int gia;

public SanPham(String ten, int gia) {
this.ten = ten;
this.gia = gia;
}

public abstract int tinhGiamGia();

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

public class SanPhamThuong extends SanPham {
public SanPhamThuong(String ten, int gia) {
super(ten, gia);
}

@Override
public int tinhGiamGia() {
return 0;
}
}

public class SanPhamVIP extends SanPham {
public SanPhamVIP(String ten, int gia) {
super(ten, gia);
}

@Override
public int tinhGiamGia() {
return gia * 20 / 100;
}
}

public class SanPhamKhuyenMai extends SanPham {
private int soTienGiam;

public SanPhamKhuyenMai(String ten, int gia, int soTienGiam) {
super(ten, gia);
this.soTienGiam = soTienGiam;
}

@Override
public int tinhGiamGia() {
return soTienGiam;
}
}

public class TestArray {
public static void main(String[] args) {
SanPham[] gioHang = {
new SanPhamThuong("Espresso", 45000),
new SanPhamVIP("Latte VIP", 50000),
new SanPhamKhuyenMai("Mocha KM", 60000, 15000)
};

System.out.println("🛒 GIỎ HÀNG:\n");

int tongTien = 0;
for (SanPham sp : gioHang) {
sp.hienThi();
tongTien += (sp.gia - sp.tinhGiamGia());
}

System.out.println("─".repeat(35));
System.out.printf("TỔNG THANH TOÁN: %,d VND%n", tongTien);
}
}

Output:

🛒 GIỎ HÀNG:

Espresso : 45,000 VND (-0)
Latte VIP : 40,000 VND (-10,000)
Mocha KM : 45,000 VND (-15,000)
───────────────────────────────────
TỔNG THANH TOÁN: 130,000 VND

Ví Dụ 6: Dynamic Method Dispatch

public class QuanCafe {
public void moC ua() {
System.out.println("🏪 Mở cửa");
}

public void phucVu() {
System.out.println("☕ Phục vụ cơ bản");
}
}

public class QuanCafeVIP extends QuanCafe {
@Override
public void moCua() {
System.out.println("🏪 Mở cửa VIP");
}

@Override
public void phucVu() {
System.out.println("🌟 Phục vụ VIP cao cấp");
}

public void phongVIP() {
System.out.println("💎 Phòng VIP riêng");
}
}

public class TestDispatch {
public static void main(String[] args) {
QuanCafe quan1 = new QuanCafe();
QuanCafe quan2 = new QuanCafeVIP(); // Upcasting

System.out.println("QUÁN THƯỜNG:");
quan1.moCua();
quan1.phucVu();

System.out.println("\nQUÁN VIP:");
quan2.moCua(); // Gọi QuanCafeVIP.moCua()
quan2.phucVu(); // Gọi QuanCafeVIP.phucVu()
// quan2.phongVIP(); // ❌ Lỗi! QuanCafe không có phongVIP()

// Downcasting để gọi phongVIP()
if (quan2 instanceof QuanCafeVIP) {
((QuanCafeVIP) quan2).phongVIP(); // ✅ OK
}
}
}

🔥 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 mau;

public HinhHoc(String mau) {
this.mau = mau;
}

public abstract double tinhDienTich();

public void ve() {
System.out.printf("🎨 Vẽ hình màu %s (S = %.2f)%n",
mau, tinhDienTich());
}
}

public class HinhTron extends HinhHoc {
private double banKinh;

public HinhTron(double banKinh, String mau) {
super(mau);
this.banKinh = banKinh;
}

@Override
public double tinhDienTich() {
return Math.PI * banKinh * banKinh;
}
}

public class HinhVuong extends HinhHoc {
private double canh;

public HinhVuong(double canh, String mau) {
super(mau);
this.canh = canh;
}

@Override
public double tinhDienTich() {
return canh * canh;
}
}

// Test
public class TestHinh {
public static void veHinh(HinhHoc hinh) {
hinh.ve(); // Polymorphic call
}

public static void main(String[] args) {
HinhHoc[] cacHinh = {
new HinhTron(5, "Đỏ"),
new HinhVuong(4, "Xanh"),
new HinhTron(3, "Vàng")
};

for (HinhHoc h : cacHinh) {
veHinh(h);
}
}
}

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

Lỗi 1: ClassCastException

DoUong d = new DoUong("Nước ép");
// Latte latte = (Latte) d; // ❌ ClassCastException!

// ✅ ĐÚNG: Kiểm tra trước
if (d instanceof Latte) {
Latte latte = (Latte) d;
} else {
System.out.println("Không phải Latte");
}

Lỗi 2: Gọi Method Không Tồn Tại

DoUong d = new Latte();
// d.themSua(); // ❌ Lỗi! DoUong không có themSua()

// ✅ ĐÚNG: Downcast hoặc dùng kiểu cụ thể
Latte latte = (Latte) d;
latte.themSua(); // ✅ OK

Lỗi 3: Quên @Override

public class Con extends Cha {
// ❌ SAI: Không dùng @Override
public void lamViec() { // Có thể nhầm tên
System.out.println("Con làm việc");
}

// ✅ ĐÚNG: Dùng @Override
@Override
public void lamViec() {
System.out.println("Con làm việc");
}
}

💡 Bí Quyết Của Barista

  1. Upcasting tự động: Cha = new Con()
  2. Downcasting thủ công: Con c = (Con) cha
  3. instanceof kiểm tra: Tránh ClassCastException
  4. Override đúng: Dùng @Override annotation
  5. Polymorphism cho linh hoạt: Code dễ mở rộng
  6. Dynamic binding: Runtime quyết định method nào

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

  • Polymorphism = Đa hình
  • ✅ Compile-time: Overloading
  • ✅ Runtime: Overriding
  • Upcasting: Cha = new Con()
  • Downcasting: (Con) cha
  • instanceof: Kiểm tra kiểu
  • Dynamic binding: Runtime quyết định
  • ✅ Polymorphism với array/collection

☕ Món Tiếp Theo

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

👉 Encapsulation - Đóng Gói


💡 Lời Khuyên Cuối: Polymorphism như menu linh hoạt - một tên gọi chung, mỗi món có cách pha riêng!