Kế Thừa Giao Diện Và Kế Thừa Cài Đặt Trong C++
Khi xây dựng các lớp giao diện và lớp triển khai trong C++, tôi chợt nhận ra mình đang đắm chìm trong một thế giới phức tạp đầy mê hoặc. Việc sử dụng các kỹ thuật hiện đại như template khiến tôi vừa hào hứng vừa băn khoăn. Đặc biệt khi hoàn thành xong, tôi lại thấy cần phải tự phê bình bản thân vì đã mải mê khám phá những điều quá mức cần thiết.
Câu chuyện bắt đầu từ bài viết trước về hệ thống garbage collection (gc) mà tôi thiết kế. Trong đó tôi đã sử dụng kỹ thuật kế thừa ảo (virtual inheritance) cùng hàm hủy ảo (virtual destructor). Những lựa chọn này có thể gây ra sự không tương thích giữa các bản biên dịch (ABI), một lý do khiến COM (Component Object Model) của Windows từ chối sử dụng các cơ chế này.
Nhắc đến COM, tôi liền hình dung đến một hệ thống khổng lồ được xây dựng từ vô số quy tắc nghiêm ngặt. Đối với kiến trúc đồ sộ như Windows, tôi chỉ có thể ngưỡng mộ từ xa mà khó lòng tiếp cận. Giá trị mà tôi học được từ những năm tháng phát triển component COM truyền thống chính là sự thận trọng trong thiết kế hệ thống, như lời cổ nhân từng nói: “Tuy chưa thể đạt tới, nhưng lòng luôn hướng về phía đó.”
Vấn đề trăn trở tôi là làm sao xây dựng hệ thống kế thừa giao diện mà không phơi bày các cơ chế kế thừa ảo hay hàm hủy ảo. Cụ thể, tôi có hệ thống giao diện phân cấp gồm iA và iB, trong đó iB kế thừa từ iA. Lớp cA triển khai giao diện iA, và tôi muốn lớp cB triển khai iB nhưng tái sử dụng phần cA đã viết.
Giải pháp đơn giản nhất là dùng kế thừa ảo:
|
|
Cơ chế này tạo ra sơ đồ kế thừa như sau:
|
|
Tuy nhiên, việc dùng virtual trong định nghĩa giao diện sẽ gây ra vấn đề tương thích giữa các trình biên dịch khác nhau, đặc biệt khi phân phối dưới dạng thư viện động.
Để giải quyết, tôi đã thử nghiệm kỹ thuật template trung gian kết hợp mã chuyển tiếp (forwarding code):
|
|
Giải pháp này dùng template tA để chuyển tiếp yêu cầu từ giao diện iA đến lớp triển khai tương ứng. Điều này đặc biệt quan trọng với hàm hủy, giúp đảm bảo cơ chế delete this hoạt động chính xác trong các phương thức của lớp cơ sở.
Câu hỏi tiếp theo là làm thế nào để mở rộng hệ thống này? Ví dụ khi có thêm giao diện iC kế thừa từ iB, làm sao để cC có thể tái sử dụng cB mà không phải viết quá nhiều mã chuyển tiếp? Tôi đã thử nghiệm giải pháp dùng template kế thừa ảo, nhưng nó nhanh chóng trở nên phức tạp đến mức không thể kiểm soát.
Kết thúc hành trình này, tôi nhận ra một chân lý giản dị: việc nghiên cứu các cơ chế phức tạp trong C++ đôi khi chỉ là sự lãng phí thời gian. Thay vào đó, hãy tập trung vào các giải pháp đơn giản, dễ bảo trì và tương thích tốt giữa các môi trường phát triển.