MST

星途 面试题库

面试题:C++ 抽象类继承在复杂设计模式中的应用

在设计一个图形绘制系统时,有一个抽象类 `Shape` 作为所有图形类的基类,包含纯虚函数 `draw()` 和 `calculateArea()`。现在要使用桥接模式对系统进行设计,使得图形的绘制方式和具体图形类型能够解耦。请用C++代码实现从 `Shape` 类派生不同具体图形类(如 `Circle`、`Rectangle`),并结合桥接模式,展示如何灵活切换绘制方式(如 `OpenGLDraw`、`VulkanDraw`),同时阐述在这种复杂设计中抽象类继承与派生规则的重要性以及可能遇到的问题和解决方案。
34.6万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

1. C++ 代码实现

绘制抽象类

class DrawAPI {
public:
    virtual void drawShape(int x, int y, int radius) = 0;
    virtual ~DrawAPI() = default;
};

OpenGL 绘制实现

class OpenGLDraw : public DrawAPI {
public:
    void drawShape(int x, int y, int radius) override {
        std::cout << "OpenGL绘制圆形在(" << x << ", " << y << ") 半径为 " << radius << std::endl;
    }
};

Vulkan 绘制实现

class VulkanDraw : public DrawAPI {
public:
    void drawShape(int x, int y, int radius) override {
        std::cout << "Vulkan绘制圆形在(" << x << ", " << y << ") 半径为 " << radius << std::endl;
    }
};

抽象图形类

class Shape {
protected:
    DrawAPI* drawAPI;
public:
    Shape(DrawAPI* drawAPI) : drawAPI(drawAPI) {}
    virtual void draw() = 0;
    virtual double calculateArea() = 0;
    virtual ~Shape() { delete drawAPI; }
};

圆形类

class Circle : public Shape {
private:
    int x, y, radius;
public:
    Circle(int x, int y, int radius, DrawAPI* drawAPI) : Shape(drawAPI), x(x), y(y), radius(radius) {}
    void draw() override {
        drawAPI->drawShape(x, y, radius);
    }
    double calculateArea() override {
        return 3.14159 * radius * radius;
    }
};

矩形类

class Rectangle : public Shape {
private:
    int x, y, width, height;
public:
    Rectangle(int x, int y, int width, int height, DrawAPI* drawAPI) : Shape(drawAPI), x(x), y(y), width(width), height(height) {}
    void draw() override {
        std::cout << "绘制矩形在(" << x << ", " << y << ") 宽为 " << width << " 高为 " << height << std::endl;
        drawAPI->drawShape(x, y, width); // 这里简单复用drawShape函数参数
    }
    double calculateArea() override {
        return width * height;
    }
};

测试代码

#include <iostream>
int main() {
    DrawAPI* opengl = new OpenGLDraw();
    DrawAPI* vulkan = new VulkanDraw();

    Shape* circle1 = new Circle(10, 10, 5, opengl);
    Shape* circle2 = new Circle(20, 20, 10, vulkan);

    circle1->draw();
    std::cout << "圆形1面积: " << circle1->calculateArea() << std::endl;

    circle2->draw();
    std::cout << "圆形2面积: " << circle2->calculateArea() << std::endl;

    delete circle1;
    delete circle2;

    return 0;
}

2. 抽象类继承与派生规则的重要性

重要性

  • 代码复用:通过继承抽象类 Shape,具体图形类(如 CircleRectangle)可以复用其定义的接口和部分属性,减少重复代码。例如,Shape 类中的 drawAPI 成员变量,具体图形类无需重复定义。
  • 多态性:抽象类定义的纯虚函数(如 draw()calculateArea()),使得派生类可以根据自身特性实现这些函数,从而在运行时根据对象的实际类型调用正确的函数,实现多态行为。这为系统提供了灵活性和扩展性,比如可以轻松添加新的图形类型。

3. 可能遇到的问题及解决方案

问题

  • 菱形继承问题:如果继承体系变得复杂,可能会出现菱形继承,导致数据冗余和歧义。例如,多个类继承自同一个抽象类,而这些类又共同继承自另一个类,会导致在最终派生类中存在多个基类副本。
  • 接口与实现的耦合:虽然桥接模式旨在解耦图形类型和绘制方式,但如果抽象类设计不当,可能会导致接口与实现之间的耦合。例如,抽象类中定义的函数过于具体,限制了派生类的实现方式。

解决方案

  • 使用虚继承解决菱形继承:在继承抽象类时,使用虚继承可以确保在最终派生类中只存在一份基类子对象,避免数据冗余和歧义。例如,class Derived : virtual public Base
  • 合理设计抽象类接口:抽象类应定义通用的、抽象的接口,避免包含过多具体实现细节。例如,Shape 类中的 draw()calculateArea() 函数定义为纯虚函数,具体实现留给派生类,这样可以最大程度地保持灵活性和解耦性。同时,通过组合(如 drawAPI 成员变量)来实现功能扩展,而不是过度依赖继承。