Menu
CoddyTech

Lặp đa hình

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 42 trên 61.

Bạn đã học cách tạo giao diện và triển khai chúng bằng nhiều hàm cụ thể khác nhau. Lợi ích thực sự xuất hiện khi bạn có thể xử lý đồng nhất một tập hợp các đối tượng khác nhau: duyệt qua chúng và gọi cùng một con trỏ hàm trên từng đối tượng, bất kể hành vi cụ thể nào được gán cho con trỏ đó.

Hãy tưởng tượng bạn có một mảng các cấu trúc Action, mỗi cấu trúc được gán một hàm execute riêng:

typedef void (*ExecuteFunc)(void);

typedef struct {
    const char* name;
    ExecuteFunc execute;
} Action;

void jump(void) { printf("Jumping!\n"); }
void run(void)  { printf("Running!\n"); }
void rest(void) { printf("Resting...\n"); }

Bạn có thể tạo một mảng chứa các hành động này, mỗi hành động được liên kết với một hàm khác nhau, rồi duyệt qua chúng bằng một vòng lặp for đơn giản:

int main() {
    Action actions[3] = {
        { "Jump", jump },
        { "Run", run },
        { "Rest", rest }
    };
    
    for (int i = 0; i < 3; i++) {
        printf("%s: ", actions[i].name);
        actions[i].execute();
    }
    return 0;
}

Vòng lặp không biết cũng không quan tâm mỗi hành động thực hiện những gì bên trong. Nó chỉ gọi execute() trên từng phần tử.

Đây là tính đa hình đang hoạt động: cùng một đoạn code xử lý các đối tượng có hành vi hoàn toàn khác nhau. Việc thêm một kiểu hành động mới không đòi hỏi thay đổi logic của lần lặp; bạn chỉ cần thêm một phần tử khác vào mảng.

challenge icon

Thử thách

Dễ

Hãy xây dựng một hệ thống quản lý Task minh họa cách lặp đa hình: xử lý một tập hợp các đối tượng có chung giao diện nhưng thực hiện các hành vi khác nhau.

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

  • task.h: Định nghĩa giao diện tác vụ tại đây. Tạo một kiểu con trỏ hàm có tên TaskFunc, không nhận tham số và không trả về gì. Sau đó, định nghĩa một struct Task có name (kiểu const char*) và con trỏ hàm run kiểu TaskFunc.
  • task.c: Viết ba hàm tác vụ khác nhau, mỗi hàm đại diện cho một loại công việc:
    • backup_task: in ra Backing up data...
    • cleanup_task: in ra Cleaning up files...
    • report_task: in ra Generating report...
  • main.c: Kết hợp mọi thứ tại đây. Tạo một mảng các struct Task, mỗi struct được gán một hàm tác vụ khác nhau. Sau đó, lặp qua mảng và thực thi từng tác vụ theo cách đa hình. Với mỗi tác vụ, in tên của tác vụ rồi gọi hàm run.

Chương trình nhận một đầu vào duy nhất: số tác vụ cần chạy (1, 2 hoặc 3).

Tạo một mảng có đúng ba tác vụ theo thứ tự sau: tác vụ sao lưu có tên Backup, tác vụ dọn dẹp có tên Cleanup và tác vụ tạo báo cáo có tên Report. Dựa trên số lượng đầu vào, chỉ lặp qua đúng số tác vụ đó, bắt đầu từ đầu mảng.

Với mỗi tác vụ trong vòng lặp, in tên tác vụ theo sau là dấu hai chấm và khoảng trắng, rồi gọi hàm run.

Đầu ra ví dụ khi đầu vào là 2:

Backup: Backing up data...
Cleanup: Cleaning up files...

Đầu ra ví dụ khi đầu vào là 3:

Backup: Backing up data...
Cleanup: Cleaning up files...
Report: Generating report...

Điểm mấu chốt là vòng lặp không cần biết từng tác vụ thực hiện việc gì. Vòng lặp chỉ cần in tên và gọi run() trên từng phần tử. Cùng một đoạn code lặp có thể xử lý đồng nhất mọi loại tác vụ. Nhớ dùng include guard trong tệp tiêu đề.

Tự làm thử

#include <stdio.h>
#include "task.h"

// Khai báo các hàm (function) tác vụ được triển khai trong task.c
void backup_task(void);
void cleanup_task(void);
void report_task(void);

int main() {
    int count;
    scanf("%d", &count);
    
    // TODO: Tạo một mảng (array) gồm 3 struct Task theo thứ tự sau:
    // 1. Tác vụ sao lưu có tên "Backup", dùng backup_task
    // 2. Tác vụ dọn dẹp có tên "Cleanup", dùng cleanup_task
    // 3. Tác vụ báo cáo có tên "Report", dùng report_task
    
    // TODO: Duyệt qua 'count' tác vụ trong mảng
    // Với mỗi tác vụ, in ra "<name>: " rồi gọi hàm run của tác vụ đó
    
    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