0

[C++ OOP Thực Chiến] Bài 39: Lưu ý cài đặt kế thừa - Những cái bẫy chết người của OOP!

Chào anh em! Ở các bài trước, chúng ta đã thấy Kế thừa là một công cụ tuyệt vời để tái sử dụng mã nguồn. Chỉ cần class Con : public Cha, thế là Lớp Con sở hữu ngay hàng tá thuộc tính và phương thức mà không cần viết lại một dòng code nào.

Chính vì sự "sung sướng" và tiện lợi đó, rất nhiều Lập trình viên mới vào nghề đã vướng vào một căn bệnh trầm kha: Nhìn đâu cũng thấy kế thừa! Hệ quả là họ tạo ra những cấu trúc Class rối rắm, phụ thuộc chằng chịt vào nhau và chỉ cần sửa một dòng code ở Lớp Cha, toàn bộ hệ thống sẽ sụp đổ.

Hãy cùng điểm mặt 3 cái bẫy chết người nhất khi thiết kế Kế thừa trong C++.

1. Cái bẫy số 1: Lạm dụng Kế thừa để tái sử dụng code (Sai bản chất IS-A)

Đây là lỗi phổ biến nhất. Hãy nhớ nguyên tắc vàng: Chỉ dùng Kế thừa khi mối quan hệ thực sự là "IS-A" (Là-Một).

Giả sử bạn đang code phần mềm cho hệ thống cổng soát vé tự động (AFC Gate). Bạn đã có sẵn một Class RFIDReader (Đầu đọc thẻ) với hàm quetThe(). Bây giờ, bạn cần viết Class Gate (Cửa soát vé) và cái cửa này cũng cần quét thẻ. Bạn thấy Class RFIDReader có sẵn hàm quetThe() hay quá, thế là bạn cho Gate kế thừa luôn RFIDReader để xài ké code!

// ❌ THIẾT KẾ SAI LẦM (Lạm dụng Kế thừa)
class Gate : public RFIDReader {
    // Bây giờ Gate nghiễm nhiên có hàm quetThe()
    // NHƯNG: Một cái Cửa Soát Vé CÓ PHẢI LÀ một Đầu Đọc Thẻ không? KHÔNG!
};

Hậu quả: Class Gate tự nhiên bị phơi bày ra những hàm cấu hình phần cứng nhạy cảm của RFIDReader (ví dụ: doiTanSoSong()), thứ mà một cái Cửa không bao giờ nên có quyền can thiệp.

Giải pháp - Chuyển sang "HAS-A" (Composition/Thành phần hóa): Cửa soát vé KHÔNG PHẢI là đầu đọc thẻ, mà nó CÓ CHỨA (HAS-A) một cái đầu đọc thẻ bên trong nó.

// ✅ THIẾT KẾ CHUẨN SENIOR (Thành phần hóa - Composition)
class Gate {
private:
    RFIDReader reader; // Nhét nó làm một thành phần (Thuộc tính)

public:
    void xuLyHanhKhach() {
        reader.quetThe(); // Gọi thông qua thành phần nội bộ
        // Mở cửa...
    }
};

Lời khuyên: Trong ngành phần mềm hiện đại, có một câu châm ngôn nổi tiếng: "Favor Composition over Inheritance" (Hãy ưu tiên Thành phần hóa hơn là Kế thừa).

2. Cái bẫy số 2: Tight Coupling (Phụ thuộc quá chặt)

Khi Lớp Con kế thừa Lớp Cha, nó bị trói chặt vào Lớp Cha (Tight Coupling). Nếu Lớp Cha thêm một tham số vào Constructor, hoặc thay đổi tên một hàm protected, thì HÀNG CHỤC Lớp Con đang kế thừa nó đều sẽ báo lỗi đỏ lòm và phải sửa code theo.

Giải pháp:

  • Hạn chế thiết kế cây kế thừa quá sâu (Tối đa chỉ nên khoảng 2-3 tầng).
  • Giữ cho Lớp Cha càng ít thuộc tính (biến) càng tốt. Lớp Cha lý tưởng nhất là Lớp Trừu tượng (Abstract Class) chỉ chứa các khung hàm giao diện (Interface). Chúng ta sẽ học cái này ở phần Đa hình (Polymorphism).

3. Cái bẫy số 3: Kế thừa Kim Cương (The Diamond Problem)

Không giống như Java hay C#, ngôn ngữ C++ có một sức mạnh cực đoan: Nó cho phép Đa kế thừa (Multiple Inheritance). Tức là một Lớp Con có thể nhận cùng lúc 2 (hoặc nhiều) Lớp Cha.

class Con : public Cha1, public Cha2

Nhưng sức mạnh này đẻ ra một con quái vật gọi là Hiệu ứng Kim Cương:

  1. Bạn có Class gốc ThietBi.

  2. Class MayIn và Class DauDocThe cùng kế thừa ThietBi.

  3. Class TicketVendingMachine (Máy bán vé TVM) kế thừa cùng lúc từ MayInDauDocThe.

    Lúc này, TicketVendingMachine sẽ nhận được 2 bản sao của Class ThietBi (một từ máy in, một từ đầu đọc thẻ). Khi bạn gọi một hàm của ThietBi, C++ sẽ bị "lú" và không biết phải gọi từ bản sao nào, dẫn đến lỗi biên dịch (Ambiguity Error).

(Để giải quyết lỗi Kim cương, C++ dùng một từ khóa cực kỳ phức tạp là virtual inheritance - Kế thừa ảo, thứ mà anh em nên tránh dùng trừ khi thực sự bắt buộc).

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

Thiết kế Kế thừa giống như đi trên một dải lụa mỏng. Bạn vừa phải đảm bảo tái sử dụng code tốt, vừa không được phép để các Class dính chặt lấy nhau một cách vô lý.

Có một chi tiết nhỏ nhưng cực kỳ quan trọng mà suốt từ Bài 35 đến giờ chúng ta vẫn luôn mặc định xài. Đó là từ khóa public đứng trước chữ Lớp Cha: class DevBackend : public NhanVien

Liệu chúng ta có thể thay chữ public đó bằng private hay protected được không? Nếu đổi thì quyền hạn của những bảo vật truyền gia từ Cha xuống Con sẽ bị "bóp méo" như thế nào?

Đây chính là chìa khóa cuối cùng để làm chủ hoàn toàn bảo mật trong OOP. Hẹn gặp lại anh em ở Bài 40: Phạm vi truy xuất trong Kế thừa - Những cánh cửa vô hình! 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í