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

news/2024/11/8 23:41:03/

目录

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.总结

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

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

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

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


http://www.ppmy.cn/news/1545488.html

相关文章

【云岚到家】-day09-2-秒杀抢购

【云岚到家】-day09-2-秒杀抢购 4.3 抢券4.3.1 解决超卖问题1&#xff09;系统需求2&#xff09;什么是超卖问题3&#xff09;悲观锁与乐观锁4&#xff09;数据库行锁控制方案5&#xff09;Redis分布式锁方案6&#xff09;Redis原子操作方案 4.3.2 Redis原子操作方案1&#xff…

Redis持久化机制——针对实习面试

目录 Redis持久化机制Redis为什么要有持久化机制&#xff1f;Redis持久化方式有哪些&#xff1f;AOF持久化工作原理是什么&#xff1f;有什么优缺点&#xff1f;AOF持久化工作原理AOF的优点AOF的缺点 RDB持久化工作原理是什么&#xff1f;有什么优缺点&#xff1f;RDB持久化工作…

Power Pivot、Power BI 和 SQL Server Analysis Services 的公式语言:DAX(数据分析表达式)

DAX&#xff08;Data Analysis Expressions&#xff09;是一种用于 Power Pivot、Power BI 和 SQL Server Analysis Services 的公式语言&#xff0c;旨在帮助用户进行数据建模和复杂计算。DAX 的设计初衷是使数据分析变得简单而高效&#xff0c;特别是在处理数据模型中的表关系…

一文了解Android本地广播

在 Android 开发中&#xff0c;本地广播&#xff08;Local Broadcast&#xff09;是一种轻量级的通信机制&#xff0c;主要用于在同一应用进程内的不同组件之间传递消息&#xff0c;而无需通过系统的全局广播机制。这种方法既可以提高安全性&#xff08;因为广播仅在应用内传播…

[Element] el-table修改滚动条上部分的背景色

[Element] el-table修改滚动条上部分的背景色 ::v-deep .el-table__cell .gutter {background: red;}

FIPS203 后量子安全ML-KEM(标准简读)

FIPS 203是美国国家标准与技术研究院&#xff08;NIST&#xff09;发布的关于模块格基密钥封装机制&#xff08;ML-KEM&#xff09;的标准&#xff0c;旨在提供一种能抵御量子计算机攻击的密钥建立方案。以下是对该文档的详细总结&#xff1a; 标准概述 目的与范围&#xff…

Pr 视频效果:超级键

视频效果/键控/超级键 Keying/Ultra Key 超级键 Ultra Key效果是 Premiere Pro 中功能强大的抠像工具&#xff0c;主要用于绿幕/蓝幕抠像。通过选择要抠除的颜色&#xff08;通常是绿幕或蓝幕的颜色&#xff09;&#xff0c;即可以将该颜色的像素设为透明&#xff0c;实现主体与…

图论基础--孤岛系列

孤岛系列有&#xff1a; 孤岛总面积求解&#xff08;用了dfs、bfs两种方法&#xff09;和沉没孤岛&#xff08;这里只写了dfs一种&#xff09; 简单解释一下&#xff1a; 题目中孤岛的定义是与边缘没有任何接触的&#xff08;也就是不和二维数组的最外圈连接&#xff09;&…