Skip to content

Đa hình (Polymorphism)

📖 Giới thiệu

Đa hình cho phép một giao diện được sử dụng cho nhiều kiểu dữ liệu khác nhau. Trong C++, đa hình được thực hiện thông qua virtual functions, function overloading và operator overloading.

Ví dụ đơn giản đầu tiên

cpp
#include <iostream>
using namespace std;

class Hinh {
public:
    virtual double tinh_dien_tich() {
        return 0;
    }
    
    virtual void ve() {
        cout << "Vẽ hình cơ bản" << endl;
    }
};

class HinhTron : public Hinh {
private:
    double ban_kinh;
    
public:
    HinhTron(double r) : ban_kinh(r) {}
    
    double tinh_dien_tich() override {
        return 3.14159 * ban_kinh * ban_kinh;
    }
    
    void ve() override {
        cout << "Vẽ hình tròn" << endl;
    }
};

class HinhVuong : public Hinh {
private:
    double canh;
    
public:
    HinhVuong(double c) : canh(c) {}
    
    double tinh_dien_tich() override {
        return canh * canh;
    }
    
    void ve() override {
        cout << "Vẽ hình vuông" << endl;
    }
};

int main() {
    Hinh* hinh1 = new HinhTron(5);
    Hinh* hinh2 = new HinhVuong(4);
    
    hinh1->ve();  // Gọi phương thức của HinhTron
    cout << "Diện tích: " << hinh1->tinh_dien_tich() << endl;
    
    hinh2->ve();  // Gọi phương thức của HinhVuong  
    cout << "Diện tích: " << hinh2->tinh_dien_tich() << endl;
    
    delete hinh1;
    delete hinh2;
    return 0;
}

🔧 Cú pháp

Virtual Functions

cpp
class LopCha {
public:
    virtual void phuong_thuc() {
        // Implementation mặc định
    }
    
    virtual ~LopCha() {}  // Virtual destructor
};

class LopCon : public LopCha {
public:
    void phuong_thuc() override {  // Override
        // Implementation mới
    }
};

Pure Virtual Functions (Abstract Class)

cpp
class LopTruuTuong {
public:
    virtual void phuong_thuc_bat_buoc() = 0;  // Pure virtual
    virtual ~LopTruuTuong() {}
};

Function Overloading

cpp
class Calculator {
public:
    int cong(int a, int b) { return a + b; }
    double cong(double a, double b) { return a + b; }
    int cong(int a, int b, int c) { return a + b + c; }
};

🔬 Phân tích & Giải thích

Các loại đa hình

1. Runtime Polymorphism (Virtual Functions)

cpp
class DongVat {
public:
    virtual void keu() {
        cout << "Động vật kêu" << endl;
    }
};

class Cho : public DongVat {
public:
    void keu() override {
        cout << "Gâu gâu!" << endl;
    }
};

class Meo : public DongVat {
public:
    void keu() override {
        cout << "Meo meo!" << endl;
    }
};

void cho_dong_vat_keu(DongVat* dv) {
    dv->keu();  // Gọi phương thức phù hợp dựa vào kiểu thực tế
}

2. Compile-time Polymorphism (Function Overloading)

cpp
class MayTinh {
public:
    int tinh(int a, int b) {
        return a + b;
    }
    
    double tinh(double a, double b) {
        return a + b;
    }
    
    string tinh(string a, string b) {
        return a + " " + b;
    }
};

3. Operator Overloading

cpp
class PhanSo {
private:
    int tu, mau;
    
public:
    PhanSo(int t = 0, int m = 1) : tu(t), mau(m) {}
    
    PhanSo operator+(const PhanSo& other) {
        return PhanSo(tu * other.mau + other.tu * mau, mau * other.mau);
    }
    
    bool operator==(const PhanSo& other) {
        return tu * other.mau == other.tu * mau;
    }
};

💻 Ví dụ minh họa

Ví dụ 1: Hệ thống thanh toán

cpp
#include <iostream>
#include <string>
#include <vector>
using namespace std;

// Lớp trừu tượng - Phương thức thanh toán
class PaymentMethod {
protected:
    string ten_phuong_thuc;
    double phi_giao_dich;
    
public:
    PaymentMethod(string ten, double phi = 0) {
        ten_phuong_thuc = ten;
        phi_giao_dich = phi;
    }
    
    virtual ~PaymentMethod() {}
    
    // Pure virtual function - bắt buộc override
    virtual bool xu_ly_thanh_toan(double so_tien) = 0;
    
    // Virtual function - có thể override
    virtual void hien_thong_tin() {
        cout << "Phương thức: " << ten_phuong_thuc << endl;
        cout << "Phí giao dịch: " << phi_giao_dich << "%" << endl;
    }
    
    // Virtual function với implementation mặc định
    virtual double tinh_phi(double so_tien) {
        return so_tien * phi_giao_dich / 100;
    }
    
    string lay_ten() const { return ten_phuong_thuc; }
};

// Lớp con 1 - Thẻ tín dụng
class TheTinDung : public PaymentMethod {
private:
    string so_the;
    string ten_chu_the;
    double han_muc;
    double da_su_dung;
    
public:
    TheTinDung(string so, string ten, double han_muc_the) 
        : PaymentMethod("Thẻ tín dụng", 2.5) {
        so_the = so;
        ten_chu_the = ten;
        han_muc = han_muc_the;
        da_su_dung = 0;
    }
    
    bool xu_ly_thanh_toan(double so_tien) override {
        double tong_tien = so_tien + tinh_phi(so_tien);
        
        if (da_su_dung + tong_tien <= han_muc) {
            da_su_dung += tong_tien;
            cout << "✅ Thanh toán thành công qua thẻ tín dụng!" << endl;
            cout << "Số tiền: " << so_tien << " VND" << endl;
            cout << "Phí: " << tinh_phi(so_tien) << " VND" << endl;
            cout << "Tổng: " << tong_tien << " VND" << endl;
            cout << "Đã sử dụng: " << da_su_dung << "/" << han_muc << " VND" << endl;
            return true;
        } else {
            cout << "❌ Vượt quá hạn mức thẻ!" << endl;
            return false;
        }
    }
    
    void hien_thong_tin() override {
        PaymentMethod::hien_thong_tin();
        cout << "Số thẻ: ****" << so_the.substr(so_the.length()-4) << endl;
        cout << "Chủ thẻ: " << ten_chu_the << endl;
        cout << "Hạn mức: " << han_muc << " VND" << endl;
        cout << "Đã sử dụng: " << da_su_dung << " VND" << endl;
    }
};

// Lớp con 2 - Ví điện tử
class ViDienTu : public PaymentMethod {
private:
    string so_dien_thoai;
    double so_du;
    string ma_pin;
    
public:
    ViDienTu(string sdt, double du_ban_dau, string pin) 
        : PaymentMethod("Ví điện tử", 1.0) {
        so_dien_thoai = sdt;
        so_du = du_ban_dau;
        ma_pin = pin;
    }
    
    bool xu_ly_thanh_toan(double so_tien) override {
        double tong_tien = so_tien + tinh_phi(so_tien);
        
        if (so_du >= tong_tien) {
            so_du -= tong_tien;
            cout << "✅ Thanh toán thành công qua ví điện tử!" << endl;
            cout << "Số tiền: " << so_tien << " VND" << endl;
            cout << "Phí: " << tinh_phi(so_tien) << " VND" << endl;
            cout << "Số dư còn lại: " << so_du << " VND" << endl;
            return true;
        } else {
            cout << "❌ Số dư không đủ!" << endl;
            return false;
        }
    }
    
    void hien_thong_tin() override {
        PaymentMethod::hien_thong_tin();
        cout << "SĐT: " << so_dien_thoai << endl;
        cout << "Số dư: " << so_du << " VND" << endl;
    }
    
    void nap_tien(double so_tien) {
        if (so_tien > 0) {
            so_du += so_tien;
            cout << "Đã nạp " << so_tien << " VND. Số dư: " << so_du << " VND" << endl;
        }
    }
};

// Lớp con 3 - Chuyển khoản ngân hàng
class ChuyenKhoanNganHang : public PaymentMethod {
private:
    string so_tai_khoan;
    string ten_ngan_hang;
    double so_du;
    
public:
    ChuyenKhoanNganHang(string so_tk, string ngan_hang, double du) 
        : PaymentMethod("Chuyển khoản ngân hàng", 0.5) {
        so_tai_khoan = so_tk;
        ten_ngan_hang = ngan_hang;
        so_du = du;
    }
    
    bool xu_ly_thanh_toan(double so_tien) override {
        double tong_tien = so_tien + tinh_phi(so_tien);
        
        if (so_du >= tong_tien) {
            so_du -= tong_tien;
            cout << "✅ Chuyển khoản thành công!" << endl;
            cout << "Số tiền: " << so_tien << " VND" << endl;
            cout << "Phí: " << tinh_phi(so_tien) << " VND" << endl;
            cout << "Từ tài khoản: " << so_tai_khoan << " (" << ten_ngan_hang << ")" << endl;
            return true;
        } else {
            cout << "❌ Số dư tài khoản không đủ!" << endl;
            return false;
        }
    }
    
    void hien_thong_tin() override {
        PaymentMethod::hien_thong_tin();
        cout << "Số TK: ****" << so_tai_khoan.substr(so_tai_khoan.length()-4) << endl;
        cout << "Ngân hàng: " << ten_ngan_hang << endl;
        cout << "Số dư: " << so_du << " VND" << endl;
    }
    
    // Override tính phí (ngân hàng có phí cố định)
    double tinh_phi(double so_tien) override {
        if (so_tien >= 1000000) {  // Miễn phí cho giao dịch lớn
            return 0;
        }
        return max(5000.0, so_tien * phi_giao_dich / 100);  // Phí tối thiểu 5000 VND
    }
};

// Class xử lý thanh toán - sử dụng polymorphism
class BillProcessor {
public:
    static void xu_ly_don_hang(vector<PaymentMethod*>& phuong_thuc_list, double so_tien) {
        cout << "\n=== XỬ LÝ ĐÓN HÀNG ===" << endl;
        cout << "Số tiền cần thanh toán: " << so_tien << " VND" << endl;
        
        cout << "\nCác phương thức thanh toán có sẵn:" << endl;
        for (int i = 0; i < phuong_thuc_list.size(); i++) {
            cout << (i + 1) << ". ";
            phuong_thuc_list[i]->hien_thong_tin();
            cout << "------------------------" << endl;
        }
        
        // Thử thanh toán với từng phương thức
        bool thanh_cong = false;
        for (PaymentMethod* pm : phuong_thuc_list) {
            cout << "\nThử thanh toán bằng " << pm->lay_ten() << ":" << endl;
            if (pm->xu_ly_thanh_toan(so_tien)) {
                thanh_cong = true;
                break;
            }
        }
        
        if (!thanh_cong) {
            cout << "\n❌ Không thể thanh toán với bất kỳ phương thức nào!" << endl;
        }
    }
    
    static void so_sanh_phi_giao_dich(vector<PaymentMethod*>& phuong_thuc_list, double so_tien) {
        cout << "\n=== SO SÁNH PHÍ GIAO DỊCH ===" << endl;
        cout << "Cho số tiền: " << so_tien << " VND" << endl;
        
        for (PaymentMethod* pm : phuong_thuc_list) {
            double phi = pm->tinh_phi(so_tien);
            cout << pm->lay_ten() << ": " << phi << " VND" << endl;
        }
    }
};

int main() {
    cout << "=== HỆ THỐNG THANH TOÁN ===" << endl;
    
    // Tạo các phương thức thanh toán
    vector<PaymentMethod*> phuong_thuc_list;
    
    phuong_thuc_list.push_back(new TheTinDung("1234567890123456", "Nguyễn Văn A", 10000000));
    phuong_thuc_list.push_back(new ViDienTu("0987654321", 5000000, "123456"));
    phuong_thuc_list.push_back(new ChuyenKhoanNganHang("1234567890", "Vietcombank", 15000000));
    
    // So sánh phí giao dịch
    BillProcessor::so_sanh_phi_giao_dich(phuong_thuc_list, 1000000);
    
    // Xử lý đơn hàng
    BillProcessor::xu_ly_don_hang(phuong_thuc_list, 2000000);
    
    cout << "\n=== NẠP THÊM TIỀN VÀO VÍ ===" << endl;
    ViDienTu* vi = dynamic_cast<ViDienTu*>(phuong_thuc_list[1]);
    if (vi) {
        vi->nap_tien(3000000);
    }
    
    // Thử lại thanh toán
    cout << "\n=== THỬ LẠI THANH TOÁN ===" << endl;
    BillProcessor::xu_ly_don_hang(phuong_thuc_list, 1500000);
    
    // Cleanup
    for (PaymentMethod* pm : phuong_thuc_list) {
        delete pm;
    }
    
    return 0;
}

Ví dụ 2: Operator Overloading - Lớp Phân số

cpp
#include <iostream>
#include <numeric>
using namespace std;

class PhanSo {
private:
    int tu_so;
    int mau_so;
    
    // Tìm ước chung lớn nhất
    int gcd(int a, int b) {
        return b == 0 ? a : gcd(b, a % b);
    }
    
    // Rút gọn phân số
    void rut_gon() {
        int ucln = gcd(abs(tu_so), abs(mau_so));
        tu_so /= ucln;
        mau_so /= ucln;
        
        // Đảm bảo mẫu số dương
        if (mau_so < 0) {
            tu_so = -tu_so;
            mau_so = -mau_so;
        }
    }
    
public:
    // Constructor
    PhanSo(int tu = 0, int mau = 1) {
        if (mau == 0) {
            cout << "Lỗi: Mẫu số không thể bằng 0!" << endl;
            tu_so = 0;
            mau_so = 1;
        } else {
            tu_so = tu;
            mau_so = mau;
            rut_gon();
        }
    }
    
    // Operator overloading for arithmetic
    PhanSo operator+(const PhanSo& other) const {
        int tu_moi = tu_so * other.mau_so + other.tu_so * mau_so;
        int mau_moi = mau_so * other.mau_so;
        return PhanSo(tu_moi, mau_moi);
    }
    
    PhanSo operator-(const PhanSo& other) const {
        int tu_moi = tu_so * other.mau_so - other.tu_so * mau_so;
        int mau_moi = mau_so * other.mau_so;
        return PhanSo(tu_moi, mau_moi);
    }
    
    PhanSo operator*(const PhanSo& other) const {
        return PhanSo(tu_so * other.tu_so, mau_so * other.mau_so);
    }
    
    PhanSo operator/(const PhanSo& other) const {
        if (other.tu_so == 0) {
            cout << "Lỗi: Không thể chia cho 0!" << endl;
            return PhanSo(0, 1);
        }
        return PhanSo(tu_so * other.mau_so, mau_so * other.tu_so);
    }
    
    // Operator overloading for comparison
    bool operator==(const PhanSo& other) const {
        return tu_so * other.mau_so == other.tu_so * mau_so;
    }
    
    bool operator!=(const PhanSo& other) const {
        return !(*this == other);
    }
    
    bool operator<(const PhanSo& other) const {
        return tu_so * other.mau_so < other.tu_so * mau_so;
    }
    
    bool operator>(const PhanSo& other) const {
        return other < *this;
    }
    
    bool operator<=(const PhanSo& other) const {
        return *this < other || *this == other;
    }
    
    bool operator>=(const PhanSo& other) const {
        return *this > other || *this == other;
    }
    
    // Operator overloading for assignment
    PhanSo& operator+=(const PhanSo& other) {
        *this = *this + other;
        return *this;
    }
    
    PhanSo& operator-=(const PhanSo& other) {
        *this = *this - other;
        return *this;
    }
    
    // Operator overloading for increment/decrement
    PhanSo& operator++() {  // Prefix ++
        tu_so += mau_so;
        return *this;
    }
    
    PhanSo operator++(int) {  // Postfix ++
        PhanSo temp = *this;
        ++(*this);
        return temp;
    }
    
    // Operator overloading for stream
    friend ostream& operator<<(ostream& os, const PhanSo& ps) {
        if (ps.mau_so == 1) {
            os << ps.tu_so;
        } else if (ps.tu_so == 0) {
            os << "0";
        } else {
            os << ps.tu_so << "/" << ps.mau_so;
        }
        return os;
    }
    
    friend istream& operator>>(istream& is, PhanSo& ps) {
        cout << "Nhập tử số: ";
        is >> ps.tu_so;
        cout << "Nhập mẫu số: ";
        is >> ps.mau_so;
        
        if (ps.mau_so == 0) {
            cout << "Mẫu số không thể bằng 0! Đặt mẫu số = 1" << endl;
            ps.mau_so = 1;
        }
        
        ps.rut_gon();
        return is;
    }
    
    // Chuyển đổi thành số thực
    double to_double() const {
        return static_cast<double>(tu_so) / mau_so;
    }
    
    // Hiển thị thông tin
    void hien_thi() const {
        cout << *this;
    }
};

// Function overloading examples
class MayTinhPhanSo {
public:
    // Overload với số lượng tham số khác nhau
    static PhanSo cong(const PhanSo& a, const PhanSo& b) {
        return a + b;
    }
    
    static PhanSo cong(const PhanSo& a, const PhanSo& b, const PhanSo& c) {
        return a + b + c;
    }
    
    static PhanSo cong(const PhanSo& a, int so_nguyen) {
        return a + PhanSo(so_nguyen);
    }
    
    // Overload với kiểu tham số khác nhau
    static void hien_thi(const PhanSo& ps) {
        cout << "Phân số: " << ps << endl;
    }
    
    static void hien_thi(double so_thuc) {
        cout << "Số thực: " << so_thuc << endl;
    }
    
    static void hien_thi(int so_nguyen) {
        cout << "Số nguyên: " << so_nguyen << endl;
    }
};

int main() {
    cout << "=== CALCULATOR PHÂN SỐ ===" << endl;
    
    // Tạo phân số
    PhanSo ps1(3, 4);
    PhanSo ps2(2, 6);  // Sẽ tự động rút gọn thành 1/3
    PhanSo ps3(5, 1);  // Hiển thị như số nguyên
    
    cout << "Phân số ban đầu:" << endl;
    cout << "ps1 = " << ps1 << endl;
    cout << "ps2 = " << ps2 << endl;
    cout << "ps3 = " << ps3 << endl;
    
    // Kiểm tra operator overloading
    cout << "\n=== PHÉP TOÁN CƠ BẢN ===" << endl;
    cout << ps1 << " + " << ps2 << " = " << (ps1 + ps2) << endl;
    cout << ps1 << " - " << ps2 << " = " << (ps1 - ps2) << endl;
    cout << ps1 << " * " << ps2 << " = " << (ps1 * ps2) << endl;
    cout << ps1 << " / " << ps2 << " = " << (ps1 / ps2) << endl;
    
    // So sánh
    cout << "\n=== SO SÁNH ===" << endl;
    cout << ps1 << " == " << ps2 << ": " << (ps1 == ps2 ? "Đúng" : "Sai") << endl;
    cout << ps1 << " > " << ps2 << ": " << (ps1 > ps2 ? "Đúng" : "Sai") << endl;
    cout << ps1 << " < " << ps3 << ": " << (ps1 < ps3 ? "Đúng" : "Sai") << endl;
    
    // Assignment operators
    cout << "\n=== PHÉP GÁN ===" << endl;
    PhanSo ps4 = ps1;
    cout << "ps4 = ps1: " << ps4 << endl;
    ps4 += ps2;
    cout << "ps4 += ps2: " << ps4 << endl;
    
    // Increment operators
    cout << "\n=== TĂNG GIẢM ===" << endl;
    cout << "ps1 trước khi tăng: " << ps1 << endl;
    cout << "++ps1: " << ++ps1 << endl;
    cout << "ps1++: " << ps1++ << endl;
    cout << "ps1 sau khi tăng: " << ps1 << endl;
    
    // Function overloading
    cout << "\n=== FUNCTION OVERLOADING ===" << endl;
    PhanSo result1 = MayTinhPhanSo::cong(ps1, ps2);
    PhanSo result2 = MayTinhPhanSo::cong(ps1, ps2, ps3);
    PhanSo result3 = MayTinhPhanSo::cong(ps1, 5);
    
    MayTinhPhanSo::hien_thi(result1);
    MayTinhPhanSo::hien_thi(result2);
    MayTinhPhanSo::hien_thi(result3);
    MayTinhPhanSo::hien_thi(3.14159);
    MayTinhPhanSo::hien_thi(42);
    
    // Nhập phân số từ người dùng
    cout << "\n=== NHẬP PHÂN SỐ MỚI ===" << endl;
    PhanSo ps_moi;
    // cin >> ps_moi;  // Uncomment để test input
    // cout << "Phân số vừa nhập: " << ps_moi << endl;
    // cout << "Giá trị thập phân: " << ps_moi.to_double() << endl;
    
    return 0;
}

🏋️ Thực hành

Bài tập 1: Abstract Media Player

Tạo lớp trừu tượng MediaPlayer và các lớp con AudioPlayer, VideoPlayer:

  • Pure virtual functions: play(), pause(), stop()
  • Mỗi player có cách xử lý khác nhau

Bài tập 2: Overload operators cho Vector3D

Tạo lớp Vector3D với operator overloading:

  • Arithmetic: +, -, * (scalar), /
  • Comparison: ==, !=
  • Stream: <<, >>

Bài tập 3: Function overloading Calculator

Tạo class Calculator với các phương thức calculate() overloaded:

  • calculate(int, int)
  • calculate(double, double)
  • calculate(string, string) // Concatenation

📋 Tóm tắt

  1. Đa hình (Polymorphism): Một giao diện, nhiều implementation

  2. Runtime Polymorphism:

    • Virtual functions
    • Pure virtual functions (abstract classes)
    • Virtual destructors
  3. Compile-time Polymorphism:

    • Function overloading
    • Operator overloading
  4. Lợi ích:

    • Code linh hoạt và có thể mở rộng
    • Dễ bảo trì
    • Tái sử dụng code hiệu quả

Bài tiếp theo: Trừu tượng hóa - Tìm hiểu về abstract classes và interfaces.

Khóa học C++ miễn phí