🎭 Đ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
- Upcasting tự động: Cha = new Con()
- Downcasting thủ công: Con c = (Con) cha
- instanceof kiểm tra: Tránh ClassCastException
- Override đúng: Dùng @Override annotation
- Polymorphism cho linh hoạt: Code dễ mở rộng
- 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:
💡 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!