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.
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ởNotifiervới:- Một thành viên
std::string recipientcó 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>
- Một thành viên
EmailNotifier.h: Định nghĩa lớpEmailNotifierkế 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ằngNotifier::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:- Tên người nhận (chuỗi)
- Nội dung thông báo (chuỗi)
Định nghĩa lớp
SMSNotifiertrự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
Notifiercơ sở,EmailNotifiervàSMSNotifier, dùng cùng một người nhận. Gọisend()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;
}
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
1Nền tảng về OOP
Tệp bên ngoàiBiên dịch và tạo bản dựng C++Tệp header và tệp nguồnNamespace và phạm viGiới thiệu về OOP trong C++Lớp và đối tượngCon trỏ thisPhương thức (hàm thành viên)Thuộc tính (dữ liệu thành viên)Cơ bản về hàm khởi tạo và hàm hủyÔn tập: máy tính đơn giản4Thuộc tính của lớp
Thành viên đối tượng và thành viên tĩnhHàm truy xuất và hàm thiết lậpHàm thành viên hằngTừ khóa mutablePhương thức và biến tĩnhHàm và lớp bạnÔn tập: Trình quản lý tài khoản ngân hàng7Kế thừa
Kế thừa cơ bảnMức truy cập trong kế thừaThứ tự gọi hàm khởi tạo và hàm hủyGhi đè phương thứcHàm ảo và VTableĐa kế thừaKế thừa ảoÔn tập: hệ phân cấp nhân viên2Quản lý bộ nhớ
Bộ nhớ ngăn xếp và heapCon trỏ và tham chiếuBộ nhớ động (new/delete)Con trỏ thông minh trong C++RAII trong C++Ôn tập: Trình quản lý mảng động5Tính đóng gói
Phạm vi truy cập trong C++Tìm hiểu sâu về phạm vi truy cậpẨn thông tinStruct và lớpLớp lồng nhau và lớp bên trongÔn tập: hệ thống hồ sơ sinh viên8Đa hình
Đa hình lúc biên dịch và khi chạyNạp chồng hàmÔn lại hàm ảoHàm ảo thuần túyLớp trừu tượngThiết kế interface trong C++Ép kiểu động và RTTIÔn tập: Máy tính hình học3Hàm khởi tạo và hàm hủy
Hàm khởi tạo mặc địnhHàm khởi tạo có tham sốHàm khởi tạo sao chépHàm khởi tạo di chuyểnDanh sách khởi tạoHàm khởi tạo ủy quyềnTìm hiểu sâu về hàm hủyQuy tắc 3 / 5 / 0Ôn tập: lớp chuỗi6Nạp chồng toán tử
Giới thiệu về nạp chồng toán tửNạp chồng toán tử số họcNạp chồng toán tử so sánhToán tử luồngNạp chồng toán tử gánNạp chồng toán tử [] và ()Toán tử chuyển đổi kiểuÔn tập: lớp ma trận9Mẫu
Mẫu hàmMẫu lớpChuyên biệt hóa mẫuMẫu biến tham sốCơ bản về SFINAE và đặc điểm kiểuÔn tập: vùng chứa tổng quátTự luyện tập: Trình biên dịch C++ online