5

Tôi thường sử dụng các lớp ảo thuần túy (giao diện) để giảm sự phụ thuộc giữa việc triển khai các lớp khác nhau trong dự án hiện tại của tôi. Nó không phải là bất thường đối với tôi thậm chí có phân cấp trong đó tôi có các lớp học ảo tinh khiết và không thuần túy mà mở rộng các lớp học ảo tinh khiết khác. Dưới đây là một ví dụ về một tình huống như vậy:Đây có phải là một quy ước tốt để hầu như kế thừa từ các lớp học ảo (giao diện) thuần túy không?

class Engine 
{ /* Declares pure virtual methods only */ } 

class RunnableEngine : public virtual Engine 
{ /* Defines some of the methods declared in Engine */ } 

class RenderingEngine : public virtual Engine 
{ /* Declares additional pure virtual methods only */ } 

class SimpleOpenGLRenderingEngine : public RunnableEngine, 
    public virtual RenderingEngine 
{ /* Defines the methods declared in Engine and RenderingEngine (that are not 
    already taken care of by RunnableEngine) */ } 

Cả RunnableEngineRenderingEngine mở rộng Engine hầu để các vấn đề kim cương không ảnh hưởng đến SimpleOpenGLRenderingEngine.

Tôi muốn có một lập trường phòng ngừa chống lại vấn đề kim cương thay vì xử lý nó khi nó trở thành vấn đề, đặc biệt là vì tôi thích viết mã dễ sử dụng nhất có thể và tôi không muốn họ phải sửa đổi các lớp của tôi để họ có thể tạo ra các lớp thừa kế cụ thể nếu Bob muốn làm điều này:

class BobsRenderingEngine : public virtual RenderingEngine 
{ /* Declares additional pure virtual methods only */ } 

class BobsOpenGLRenderingEngine : public SimpleOpenGLRenderingEngine, 
    public BobsRenderingEngine 
{ /* Defines the methods declared in BobsRenderingEngine */ } 

này sẽ không thực hiện được nếu tôi đã không được thực hiện SimpleOpenGLRenderingEngine mở rộng RenderingEngine hầu. Tôi biết rằng xác suất của Bob muốn thực hiện việc này có thể rất thấp.

Vì vậy, tôi đã bắt đầu sử dụng quy ước bao giờ cũng mở rộng các lớp ảo thuần túy để đa thừa kế từ chúng không gây ra vấn đề về kim cương. Có lẽ điều này là do tôi đến từ Java và có xu hướng chỉ sử dụng thừa kế đơn với các lớp học ảo không thuần túy. Tôi chắc chắn nó có lẽ là quá mức cần thiết trong một số tình huống nhưng có bất kỳ nhược điểm nào khi sử dụng quy ước này không? Điều này có thể gây ra bất kỳ vấn đề với hiệu suất/chức năng, vv? Nếu không, tôi không thấy lý do không sử dụng quy ước, ngay cả khi nó có thể không cần đến cuối cùng.

+3

Yup, hoàn thành quá mức cần thiết. Nó chỉ là một vấn đề kim cương thực khi bạn có nhiều * triển khai * để lựa chọn. Không phải là vấn đề với giao diện, nó không có bất kỳ triển khai nào. Thừa kế ảo là một băng hỗ trợ, bạn chỉ sử dụng nó khi lưng của bạn chống lại bức tường. –

+2

@ HansPassant, tôi không đồng ý với bạn. Dimond vấn đề thường có thể xảy ra ngay cả với các lớp học ảo tinh khiết. Giả sử bạn có một giao diện, nói IA, và thực thi mặc định của nó 'AImpl: public virtual IA', sau đó sử dụng thừa kế ảo, bạn có thể định nghĩa một 'lớp B: public virtual IA, public AImpl'. Do đó B sẽ sử dụng IA của thực hiện AImpl. –

+0

@kids_fok Yup, đó chính xác là nơi tôi gặp vấn đề. Vâng, tôi không chắc chắn bạn cần 'B' để mở rộng' IA' một cách rõ ràng trong ví dụ của bạn bởi ví dụ trong câu hỏi của tôi là tương tự. –

Trả lời

2

Tôi sẽ nói rằng, nói chung, khuyến khích thừa kế là một ý tưởng tồi (và đó là những gì bạn đang làm bằng cách sử dụng thừa kế ảo). Vài điều thực sự được đại diện tốt với một cấu trúc cây được ngụ ý bởi thừa kế. Ngoài ra tôi thấy rằng nhiều thừa kế có xu hướng phá vỡ quy tắc trách nhiệm duy nhất. Tôi không biết trường hợp sử dụng chính xác của bạn và tôi đồng ý rằng đôi khi chúng tôi không có lựa chọn nào khác.

Tuy nhiên trong C++, chúng tôi có cách khác để soạn đối tượng, đóng gói. Tôi thấy rằng việc kê khai dưới đây là xa dễ dàng hơn để hiểu và vận dụng:

class SimpleOpenGLRenderingEngine 
{ 
public: 
    RunnableEngine& RunEngine() { return _runner; } 
    RenderingEngine& Renderer() { return _renderer; } 

    operator RunnableEngine&() { return _runner; } 
    operator RenderingEngine&() { return _renderer; } 

private: 
    RunnableEngine _runner; 
    RenderingEngine _renderer; 
}; 

Đúng là đối tượng sẽ sử dụng bộ nhớ hơn so với thừa kế ảo, nhưng tôi nghi ngờ các đối tượng mà phức tạp được tạo ra với số lượng lớn.

Bây giờ, giả sử bạn thực sự muốn kế thừa, đối với một số ràng buộc bên ngoài. Thừa kế ảo vẫn còn khó để thao tác: bạn có thể cảm thấy thoải mái với nó, nhưng mọi người bắt nguồn từ các lớp của bạn có thể không, và có lẽ sẽ không suy nghĩ quá nhiều trước khi bắt nguồn. Tôi đoán một sự lựa chọn tốt hơn sẽ là sử dụng thừa kế riêng.

class RunnableEngine : private Engine 
{ 
    Engine& GetEngine() { return *this; } 
    operator Engine&() { return *this; } 
}; 

// Similar implementation for RenderingEngine 

class SimpleOpenGLRenderingEngine : public RunnableEngine, public RenderingEngine 
{ }; 
2

Câu trả lời ngắn: Có. Bạn đóng đinh nó.

Long trả lời:

Vì vậy, tôi đã bắt đầu sử dụng các quy ước của luôn mở rộng các lớp học ảo tinh khiết

Lưu ý: thuật ngữ thích hợp là trừu tượng lớp học, không phải là "lớp học ảo tinh khiết ".

Tôi đã bắt đầu sử dụng quy ước luôn mở rộng các lớp trừu tượng để đa thừa kế từ chúng không gây ra vấn đề về kim cương.

Thật vậy. Đây là một quy ước âm thanh, nhưng không chỉ cho các lớp trừu tượng, cho tất cả các lớp đại diện cho một giao diện, một số trong đó có các chức năng ảo với các triển khai mặc định.

(Những thiếu sót của lực lượng Java bạn chỉ đưa khái niệm hàm ảo thuần túy và không có thành viên dữ liệu trong "giao diện", C++ là linh hoạt hơn, cho tốt, như thường lệ.)

thể làm điều này bất kỳ vấn đề với hiệu suất

Ảo có chi phí.

Cấu hình mã của bạn.

Điều này có thể gây ra bất kỳ sự cố nào với chức năng [], v.v. không?

Có thể (hiếm khi). Bạn không thể unvirtualise một lớp cơ sở: nếu một số lớp dẫn xuất cụ thể cần phải có hai subobject lớp cơ sở riêng biệt, bạn không thể sử dụng thừa kế cho cả hai chi nhánh, bạn cần ngăn chặn.

1

Vâng, nó khá đơn giản. Bạn đã vi phạm nguyên tắc trách nhiệm duy nhất (SPR) và đây là nguyên nhân của vấn đề của bạn. Lớp "Engine" của bạn là một lớp Thiên Chúa. Một hệ thống phân cấp được thiết kế tốt không gọi vấn đề này.

Các vấn đề liên quan