+4

[C++ OOP Thực Chiến] Bài 35: Đặc tính cơ bản của kế thừa đơn - Sự truyền ngôi của các Class!

Chào anh em! Hãy tưởng tượng bạn đang viết phần mềm quản lý nhân sự cho một công ty IT. Ban đầu, bạn tạo một Class NhanVien với các thuộc tính cơ bản: hoTen, tuoi, luongCoBan và hàm chamCong().

Sau đó, công ty tuyển thêm vị trí Kỹ sư Backend. Bạn cần một Class DevBackend. Anh Dev này cũng là nhân viên, nên anh ta cũng có họ tên, tuổi, lương cơ bản và cũng phải chấm công. Nhưng anh ta lại có thêm kỹ năng đặc thù là ngonNguCode (Golang, PHP) và hàm deployServer().

Chẳng lẽ bạn lại mở một file mới, gõ lại y chang mớ code của NhanVien rồi mới nhét thêm ngonNguCode vào? Quá cồng kềnh! C++ cho phép Class DevBackend xin "kế thừa" lại toàn bộ tài sản của Class NhanVien.

1. Kế thừa (Inheritance) là gì?

Kế thừa là cơ chế cho phép một Class mới (Lớp con) được tạo ra dựa trên một Class đã tồn tại (Lớp cha).

  • Lớp cha (Base Class / Super Class): Nơi chứa những đặc điểm chung nhất. (Ví dụ: NhanVien)
  • Lớp con (Derived Class / Sub Class): Nhận lại toàn bộ tài sản của Lớp cha, đồng thời có thể "độ" thêm những vũ khí, thuộc tính của riêng nó. (Ví dụ: DevBackend)

Mối quan hệ "IS-A" (Là một): Kế thừa chỉ hợp lý khi bạn có thể nói: "Lớp Con LÀ MỘT Lớp Cha".

  • DevBackend LÀ MỘT NhanVien -> Hợp lý! Cho kế thừa.
  • NhanVien LÀ MỘT DevBackend -> Sai! (Có người là HR, Tester).

2. Cú pháp truyền ngôi (Kế thừa đơn)

"Kế thừa đơn" nghĩa là một Lớp con chỉ có duy nhất một Lớp cha (1 cha 1 con). Cú pháp trong C++ sử dụng dấu hai chấm : như sau:

class LopCon : public LopCha {
    // Các thuộc tính và phương thức mới bổ sung thêm
};

Từ khóa public ở đây là một Access Modifier (Bổ ngữ truy cập) dùng cho kế thừa. Nó quyết định xem tài sản của cha khi truyền xuống cho con sẽ bị biến đổi quyền hạn như thế nào. Hiện tại, chúng ta luôn dùng public để giữ nguyên bản chất của tài sản.

3. Code Demo: Quản lý nhân sự công ty công nghệ

Hãy cùng xem mã nguồn của chúng ta được "rút gọn" thần kỳ như thế nào khi có Kế thừa:

#include <iostream>
#include <string>

using namespace std;

// --- LỚP CHA (BASE CLASS) ---
class NhanVien {
public: // Tạm thời dùng public để lớp con dễ dàng truy cập
    string hoTen;
    double luongCoBan;

    NhanVien(string ten, double luong) {
        hoTen = ten;
        luongCoBan = luong;
    }

    void chamCong() {
        cout << "[System] Nhan vien " << hoTen << " da diem danh luc 8:00 AM.\n";
    }
};

// --- LỚP CON (DERIVED CLASS) ---
// DevBackend kế thừa toàn bộ tài sản của NhanVien
class DevBackend : public NhanVien {
public:
    string ngonNguCode; // Thuộc tính bổ sung của riêng Dev

    // Constructor của lớp con phải gọi Constructor của lớp cha
    DevBackend(string ten, double luong, string ngonNgu) 
        : NhanVien(ten, luong) // Truyền dữ liệu ngược lên cho Cha xây dựng
    {
        ngonNguCode = ngonNgu;
    }

    // Phương thức bổ sung của riêng Dev
    void deployServer() {
        cout << "[Action] " << hoTen << " dang deploy he thong bang " << ngonNguCode << "...\n";
    }
};

int main() {
    cout << "--- HE THONG QUAN LY NHAN SU ---\n\n";

    // 1. Tạo một nhân viên bình thường
    NhanVien hr("Nguyen Van A", 1000.0);
    hr.chamCong();
    // hr.deployServer(); // LỖI NGAY! Nhân viên thường không biết deploy!

    cout << "\n-------------------\n\n";

    // 2. Tạo một Dev Backend
    DevBackend dev("Tran Van B", 2500.0, "Golang & PHP");
    
    // ĐIỀU KỲ DIỆU CỦA KẾ THỪA:
    // Mặc dù trong class DevBackend không hề có hàm chamCong() và biến hoTen,
    // nhưng object 'dev' vẫn có thể xài được vì đã thừa kế từ Lớp cha!
    dev.chamCong(); 
    
    // Gọi kỹ năng riêng của Lớp con
    dev.deployServer();

    return 0;
}

Nhận xét: Anh em có thấy hàm chamCong() không hề được viết lại trong class DevBackend nhưng vẫn gọi được mượt mà không? Đây chính là sức mạnh của tái sử dụng mã nguồn. Nếu sau này hệ thống thay đổi giờ chấm công từ 8:00 thành 8:30, bạn chỉ cần sửa ở ĐÚNG MỘT NƠI (Lớp cha), toàn bộ các Lớp con sẽ tự động cập nhật theo!

Tạm kết & Gợi mở

Chúng ta vừa dạo bước qua khái niệm cơ bản nhất của kế thừa. Cơ chế này giúp thiết kế hệ thống trở nên phân tầng, rõ ràng và cực kỳ dễ bảo trì.

Nhưng khoan đã, hãy nhìn lại class NhanVien (Lớp cha) trong đoạn code trên. Mình đã cố tình đặt hoTenluongCoBan ở chế độ public để Lớp con DevBackend dễ lấy ra xài trong hàm deployServer(). Nhưng ở các bài trước, chúng ta đã thề non hẹn biển rằng: "Dữ liệu luôn luôn phải nằm trong private để đảm bảo Đóng gói (Encapsulation)!"

Vậy nếu chúng ta đưa hoTen về lại private, liệu thằng Lớp con có còn nhìn thấy và xài được tài sản của cha nó nữa không? Quy luật của C++ khắc nghiệt đến mức nào?

Sự xung đột nảy lửa giữa Đóng gói và Kế thừa sẽ được giải quyết trong bài tiếp theo. Hẹn gặp lại anh em ở Bài 36: Đặc tính cơ bản của kế thừa đơn (Phần 2) - Cú lừa của private và sự xuất hiện của protected! Nhớ thả Upvote để tiếp lửa cho series nhé!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí