自学内容网 自学内容网

C++代码优化(二): 区分接口继承和实现继承

目录

1.引言

2.接口继承

3.实现继承

4.如何选择接口继承与实现继承

5.完整实例

6.总结


1.引言

        在C++中,区分接口继承和实现继承是一种良好的编程实践,有助于提高代码的可维护性、可读性和可扩展性。接口继承通常指的是从基类继承纯虚函数(pure virtual functions),而实现继承则是从基类继承具体的实现。接口继承和实现继承之间的区别在于,它们分别用于不同的目的:前者用于定义行为,后者用于共享实现。

2.接口继承

        接口继承通常用于定义一个抽象基类,其中只包含纯虚函数。这个基类不能被实例化,只能作为其他类的基类使用。

        这种方式通常用于定义一组行为约定,保证所有派生类都实现相同的行为,但实现的细节可以各自不同。

      特点:

  • 基类只定义接口,不提供任何实现。

  • 派生类必须实现基类中声明的所有纯虚函数。

  • 主要目的是为了多态性,通过基类指针或引用调用派生类的方法。

// 纯虚类,用作接口
class Shape {
public:
    virtual ~Shape() = default;
    virtual void draw() const = 0;  // 纯虚函数,派生类必须实现
};

class Circle : public Shape {
public:
    void draw() const override {
        // Circle 特有的实现
        std::cout << "Drawing Circle" << std::endl;
    }
};

class Rectangle : public Shape {
public:
    void draw() const override {
        // Rectangle 特有的实现
        std::cout << "Drawing Rectangle" << std::endl;
    }
};

在这个例子中,Shape 类定义了一个纯虚函数 draw(),用于描述绘制形状的行为,但没有提供实现。Circle 和 Rectangle 是具体实现类,负责实现接口中的 draw() 函数。客户端代码可以通过基类指针来调用具体类的实现,利用多态性实现灵活的代码设计。

优点:

  • 派生类必须提供实现,保证了不同派生类实现同样的行为。

  • 可以在接口层面定义抽象概念,解耦具体实现。

缺点:

  • 如果没有具体实现,可能会产生重复代码,导致冗余实现。

3.实现继承

        实现继承是指基类不仅定义接口,还提供某些功能的默认实现。派生类可以直接继承这些功能,或者根据需要选择覆盖(override)它们。

        这种方式通常用于减少代码重复,提供通用的功能实现,派生类可以选择复用基类的实现,也可以根据具体情况进行覆盖。

     特点:

  • 基类既定义接口,也提供某些函数的实现。

  • 派生类可以复用基类中的实现,也可以选择覆盖(override)基类的实现。

  • 主要目的是代码复用,减少重复实现。

class Shape {
public:
    virtual ~Shape() = default;

    // 提供默认实现
    virtual void draw() const {
        std::cout << "Drawing a generic shape" << std::endl;
    }

    // 需要派生类实现的纯虚函数
    virtual double area() const = 0;
};

class Circle : public Shape {
public:
    void draw() const override {
        // 调用基类实现,避免重复代码
        Shape::draw();
        std::cout << "Specifically drawing a circle" << std::endl;
    }

    double area() const override {
        return 3.14 * radius * radius;
    }

private:
    double radius = 1.0;
};

在这个例子中,Shape 类不仅定义了接口,还提供了一个默认的 draw() 实现,派生类 Circle 可以调用基类的实现,同时扩展自己特有的行为。Circle 也可以选择覆盖 draw() 函数,改变绘制行为。

优点:

  • 提供了代码复用机制,减少重复代码。

  • 派生类可以在需要时覆盖基类中的实现,灵活性较高。

缺点:

  • 基类的实现可能与派生类不完全匹配,派生类可能需要额外工作去适应。

  • 如果不当使用,可能导致派生类对基类实现的过度依赖。

4.如何选择接口继承与实现继承

        类设计时,接口继承与实现继承相互独立,代表着一定的设计意义,在二者之间进行选择时,我们需要考虑的因素:

        1) 对于无法提供默认版本的函数接口选择函数接口继承,对于能够提供默认版本的函数接口,选择函数实现继承。

        2) 如果你需要强制派生类实现某些行为,而基类不关心实现细节,使用接口继承;如果基类中有可以复用的代码实现,且派生类可能会依赖它,使用实现继承。

5.完整实例

#include <iostream>
#include <vector>
#include <memory>

// 接口继承
class IShape {
public:
    virtual ~IShape() = default;
    virtual void draw() const = 0;
    virtual double area() const = 0;
};

// 实现继承
class Shape : public IShape {
public:
    void draw() const override {
        std::cout << "Drawing a generic shape" << std::endl;
    }
    
    // 具体形状必须提供自身的面积计算方式
    virtual double area() const = 0;
};

class Circle : public Shape {
public:
    Circle(double r) : radius(r) {}

    // 覆盖并调用基类方法
    void draw() const override {
        Shape::draw();  // 调用基类的默认行为
        std::cout << "Drawing a circle with radius " << radius << std::endl;
    }

    double area() const override {
        return 3.14 * radius * radius;
    }

private:
    double radius;
};

class Rectangle : public Shape {
public:
    Rectangle(double w, double h) : width(w), height(h) {}

    void draw() const override {
        Shape::draw();
        std::cout << "Drawing a rectangle with width " << width << " and height " << height << std::endl;
    }

    double area() const override {
        return width * height;
    }

private:
    double width, height;
};

int main() {
    std::vector<std::shared_ptr<IShape>> shapes;
    shapes.push_back(std::make_shared<Circle>(5.0));
    shapes.push_back(std::make_shared<Rectangle>(3.0, 4.0));

    for (const auto& shape : shapes) {
        shape->draw();
        std::cout << "Area: " << shape->area() << std::endl;
    }

    return 0;
}

6.总结

        在面向对象编程中,接口继承和实现继承是两种不同的继承方式,它们的区别在于继承的成员的特性不同,分别对应了不同的编程需求。

        接口继承是指派生类只继承了基类的接口(也就是纯虚函数),而没有继承基类的实现。这种方式使得派生类必须实现基类中的所有纯虚函数,从而使得派生类和基类的实现是分离的,实现了接口和实现的分离。这种继承方式常常用于实现抽象类和接口,强制要求派生类实现接口中的所有函数。

        实现继承是指派生类继承了基类的接口和实现,包括数据成员和函数实现。这种方式使得派生类可以复用基类的代码,从而减少了代码的重复编写,同时也保证了派生类和基类的一致性。但是,这也意味着派生类和基类的实现是紧密耦合的,基类的修改可能会影响到派生类的行为。

        因此,接口继承和实现继承各有其优缺点,需要根据具体的编程需求来选择合适的继承方式。如果需要实现接口或抽象类,或者需要避免实现的紧密耦合,那么应该选择接口继承;如果需要复用代码,并且基类的实现不会被修改,那么可以考虑使用实现继承。


原文地址:https://blog.csdn.net/haokan123456789/article/details/143635107

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!