Menu
CoddyTech

Ghi đè phương thức

Thuộc phần Lập trình hướng đối tượng trong hành trình C++ của Coddy. Bài học 51 trên 104.

Ghi đè phương thức xảy ra khi một lớp dẫn xuất cung cấp cách triển khai riêng cho một phương thức đã có trong lớp cơ sở. Điều này cho phép các lớp dẫn xuất tùy chỉnh hành vi kế thừa mà vẫn giữ nguyên chữ ký phương thức.

Để ghi đè một phương thức (method), bạn chỉ cần định nghĩa một phương thức trong lớp dẫn xuất có cùng tên, kiểu trả về và các tham số:

class Animal {
public:
    void speak() {
        std::cout << "Some sound" << std::endl;
    }
};

class Dog : public Animal {
public:
    void speak() {
        std::cout << "Woof!" << std::endl;
    }
};

Dog d;
d.speak();  // Đầu ra: Woof!

Phiên bản trong lớp dẫn xuất che khuất phiên bản trong lớp cơ sở. Tuy nhiên, bạn vẫn có thể truy cập phương thức của lớp cơ sở bằng toán tử phân giải phạm vi:

class Dog : public Animal {
public:
    void speak() {
        Animal::speak();  // Gọi phiên bản của lớp cơ sở
        std::cout << "Woof!" << std::endl;
    }
};

Cách ghi đè cơ bản này có một hạn chế quan trọng. Khi bạn truy cập một đối tượng dẫn xuất thông qua con trỏ hoặc tham chiếu đến lớp cơ sở, phiên bản của lớp cơ sở sẽ được gọi thay thế:

Dog d;
Animal* ptr = &d;
ptr->speak();  // Đầu ra: Some sound (không phải Woof!)

Điều này xảy ra vì trình biên dịch xác định phương thức cần gọi dựa trên kiểu con trỏ, chứ không phải kiểu đối tượng thực tế. Để có tính đa hình thực sự khi chạy, trong đó phương thức phù hợp được gọi dựa trên đối tượng thực tế, bạn cần dùng hàm ảo (virtual function), nội dung này sẽ được đề cập trong bài học tiếp theo.

challenge icon

Thử thách

Dễ

Hãy xây dựng một hệ thống thông báo minh họa cách ghi đè phương thức (method overriding). Bạn sẽ tạo lớp cơ sở Notifier và hai lớp thông báo chuyên biệt để tùy chỉnh cách gửi thông báo.

Bạn sẽ sắp xếp code trong ba tệp:

  • Notifier.h: Định nghĩa lớp cơ sở Notifier với:
    • Một thành viên std::string recipient có quyền truy cập protected
    • Một hàm khởi tạo nhận tên người nhận và lưu tên đó
    • Một phương thức send(const std::string& message) công khai, in ra: Sending to <recipient>: <message>
  • EmailNotifier.h: Định nghĩa lớp EmailNotifier kế thừa công khai từ Notifier:
    • Một hàm khởi tạo nhận người nhận và truyền giá trị đó cho lớp cơ sở
    • Ghi đè phương thức send() để gọi phiên bản của lớp cơ sở bằng Notifier::send() trước, sau đó in ra: [Email sent successfully]
  • main.cpp: Đọc hai đầu vào, mỗi đầu vào trên một dòng riêng:
    1. Tên người nhận (chuỗi)
    2. Nội dung thông báo (chuỗi)

    Định nghĩa lớp SMSNotifier trực tiếp trong main.cpp, lớp này kế thừa công khai từ Notifier:

    • Một hàm khởi tạo nhận người nhận và truyền giá trị đó cho lớp cơ sở
    • Ghi đè phương thức send() để in ra: SMS to <recipient>: <message>, thay thế hoàn toàn cách hoạt động của lớp cơ sở mà không gọi phương thức đó

    Tạo một đối tượng cho mỗi kiểu thông báo, gồm Notifier cơ sở, EmailNotifier và SMSNotifier, dùng cùng một người nhận. Gọi send() trên từng đối tượng với thông báo đầu vào, đồng thời in một dòng phân cách giữa các lần gọi:

    --- Base Notifier ---
    <output>
    --- Email Notifier ---
    <output>
    --- SMS Notifier ---
    <output>

Ví dụ, với đầu vào Alice và Hello there!:

--- Base Notifier ---
Sending to Alice: Hello there!
--- Email Notifier ---
Sending to Alice: Hello there!
[Email sent successfully]
--- SMS Notifier ---
SMS to Alice: Hello there!

Lưu ý cách EmailNotifier mở rộng cách hoạt động của lớp cơ sở bằng cách gọi phương thức của lớp cha trước, trong khi SMSNotifier thay thế hoàn toàn phương thức đó bằng cách triển khai riêng. Đừng quên header guard và thêm các header phù hợp vào từng tệp.

Tự làm thử

#include <iostream>
#include <string>
#include "Notifier.h"
#include "EmailNotifier.h"

using namespace std;

// TODO: Định nghĩa lớp (class) SMSNotifier tại đây, kế thừa công khai từ Notifier
// Hàm khởi tạo (constructor) truyền recipient cho lớp cơ sở
// Ghi đè send() để in ra: SMS to <recipient>: <message>
//   (thay thế hoàn toàn hành vi của lớp cơ sở, không gọi send() của lớp cha)

int main() {
    string recipient;
    string message;
    
    getline(cin, recipient);
    getline(cin, message);
    
    // TODO: Tạo đối tượng (object) Notifier với recipient
    // In ra "--- Base Notifier ---"
    // Gọi send() với message
    
    // TODO: Tạo đối tượng EmailNotifier với recipient
    // In ra "--- Email Notifier ---"
    // Gọi send() với message
    
    // TODO: Tạo đối tượng SMSNotifier với recipient
    // In ra "--- SMS Notifier ---"
    // Gọi send() với message
    
    return 0;
}
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch C++ online