c++ - 类 Square 应该从类 Rectangle 公开继承吗?

标签 c++ inheritance public

在阅读了关于公共(public)继承的“Effective C++”部分后,我发现这个问题非常有趣。在我说是之前,这是常识,因为每个方块 矩形,但不一定是其他方式。但是请考虑以下代码:

void makeBigger(Rectangle& r) { 

    r.setWidth(r.width() + 10); 

} 

此代码非常适合 Rectangle ,但会破坏 Square如果我们将对象传递给 makeBigger - 它的双方将变得不平等。

那么我该如何处理呢? 这本书没有提供答案(还没有?),但我正在考虑解决这个问题的几种方法:
  • 覆盖 setWidth()setHeight() Square 中的方法类(class)也调整了对方。

    缺点:代码重复,不需要Square的2个成员.
  • 对于 Square不继承自 Rectangle并独立 - 拥有 size , setSize()等等。

    缺点:奇怪 - 正方形毕竟是矩形 - 重用会很好Rectangle的特征,如直角等。
  • 制作 Rectangle抽象(通过给它一个纯虚拟析构函数并定义它)并有第三个类来表示不是正方形的矩形并继承自 Rectangle .这将迫使我们将上述函数的签名更改为:
    void makeBigger(NotSquare& r);
    除了有额外的类(class)外,看不出任何缺点。


  • 有没有更好的办法?我倾向于第三种选择。

    最佳答案

    这是我发现处理不当的面向对象设计的关键原则之一。迈耶先生在讨论您所指的这本书方面做得非常出色。

    诀窍是记住这些原则必须应用于具体的用例。使用继承时,请记住,关键是当您想将该对象用作...时,“is a”关系适用于该对象,因此正方形是否为矩形取决于您将要做什么将来有矩形。

    如果您将独立设置矩形的宽度和高度,那么不,正方形不是矩形(在您的软件上下文中),尽管它是数学上的。因此,您必须考虑将如何处理基础对象。

    在您提到的具体示例中,有一个规范的答案。如果让 makeBigger 成为矩形的虚成员函数,那么每个函数都可以以适合类的方式进行缩放。但是,如果适用于矩形的所有(公共(public))方法都适用于正方形,那么这只是好的 OO 设计。

    因此,让我们看看这如何适用于您迄今为止的努力:

  • 我经常在生产代码中看到这种事情。在其他良好的设计中修复差距是可以原谅的,但这是不可取的。但这是一个问题,因为它导致代码在语法上正确,但在语义上不正确。它会编译,并做一些事情,但意思是不正确的。假设您正在迭代一个矩形 vector ,并将宽度缩放 2,将高度缩放 ​​3。这对于正方形在语义上没有意义。因此它违反了“优先编译时错误而不是运行时错误”的原则。
  • 在这里,您正在考虑使用继承来重用代码。有句话叫“用继承是要重用,不是要重用”。这意味着,您希望使用继承来确保 oo 代码可以在其他地方重用,作为其基础对象,而无需任何手动 rtti。请记住,还有其他代码重用机制:在 C++ 中,这些包括函数式编程和组合。

    如果正方形和矩形具有共享代码(例如,根据它们具有直角的事实计算面积),则可以通过组合来实现(每个都包含一个公共(public)类)。在这个简单的例子中,你可能最好使用一个函数,例如:
    compute_area_for_rectangle(Shape* s){return s.GetHeight() * s.GetWidth());}
    在命名空间级别提供。

    因此,如果 Square 和 Rectangle 都继承自 Shape 基类,Shape 具有以下公共(public)方法:draw()、scale()、getArea() ...,所有这些对于任何形状都具有语义意义,并​​且通用公式可以通过命名空间级函数共享。
  • 我想如果你稍微思考一下这一点,你会发现你的第三个建议有很多缺陷。

    关于 oo 设计的观点:正如 icbytes 提到的,如果你打算有第三个类,这个类是一个有意义地表达常见用途的公共(public)基础更有意义。形状还可以。如果主要目的是绘制对象,那么 Drawable 可能是另一个好主意。

    你表达这个想法的方式还有其他一些缺陷,这可能表明你对虚拟析构函数有误解,以及抽象意味着什么。每当您将类的方法设为 virtual 以便另一个类可以覆盖它时,您也应该将析构函数声明为 virtual (S.M. 确实在 Effective C++ 中讨论了这一点,所以我想您会自己发现这一点)。这并不使它抽象。当您声明至少一种纯虚拟方法时,它变得抽象 - 即没有实现
    虚拟无效 foo() = 0;//例如
    这意味着有问题的类不能被实例化。显然,因为它至少有一个虚方法,它也应该有声明为虚的析构函数。

  • 我希望这有帮助。请记住,继承只是一种可以重用代码的方法。好的设计来自所有方法的最佳组合。

    为了进一步阅读,我强烈推荐 Sutter 和 Alexandrescu 的“C++ 编码标准”,尤其是关于类设计和继承的部分。第34条“优先组合而不是继承”和第37条“公共(public)继承是可替代性。继承,不是重用,而是被重用。”

    关于c++ - 类 Square 应该从类 Rectangle 公开继承吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18888693/

    相关文章:

    class - 使用JavaScript ES6/ES2015子类化时如何覆盖继承的方法

    c++ - 菱形继承(钻石问题) - 从抽象类和具体类继承而不是实现基于共享的类

    C++ 友元 vs 公共(public)

    c++ - 使用 Spirit Parser Framework 处理转义

    c++ - 使用引用或指针通过 xor 运算符交换两个变量的值

    c++ - reinterpret_cast 到 C++ 中的聚合类型

    c# - EF代码优先中使用继承的困境

    c++ - 如何使用公共(public)函数访问私有(private)变量?

    javascript - 公共(public)变量未正确更改

    c++ - 关于 C++ 中的内存