Ole*_*siy 4 c++ inheritance public
在阅读了关于公共继承的"有效C++"部分后,我发现这个问题非常有趣.之前我会说是的常识,因为每个方格都是一个矩形,但不一定是其他方式.但请考虑以下代码:
void makeBigger(Rectangle& r) {
r.setWidth(r.width() + 10);
}
Run Code Online (Sandbox Code Playgroud)
这个代码对于a来说完全没问题Rectangle,但Square如果我们将它传递给makeBigger它会破坏对象- 它的边会变得不相等.
那我怎么处理这个呢? 这本书没有提供答案(但是?),但我想到了几种解决方法:
在类中重写setWidth()和setHeight()方法Square也可以调整另一方.
缺点:代码重复,不必要的2个成员Square.
对于Square不从继承Rectangle,并在自己的-有size,setSize()等等.
缺点:奇怪 - 方块毕竟是矩形 - 重用Rectangle直角等特征会很好.
使Rectangle抽象(通过赋予它一个纯虚拟析构函数并定义它)并使用第三个类来表示不是正方形并继承的矩形Rectangle.这将迫使我们将上述函数的签名更改为:
void makeBigger(NotSquare& r);
除了额外的课程外,看不出任何缺点.
有没有更好的办法?我倾向于第三种选择.
Spa*_*ose 10
这是OO设计中的一个关键原则,我发现它被错误处理.迈耶先生非常出色地讨论了你所指的那本书.
诀窍是要记住原则必须应用于具体的用例.使用继承时,请记住,当您想要将该对象用作 ... 时,关键是"是一个"关系适用于对象...因此,方块是否为矩形取决于您将要执行的操作将来会有矩形.
如果您将独立设置矩形的宽度和高度,则不是,方形不是矩形(在软件的上下文中),尽管它是数学上的.因此,您必须考虑将对基础对象执行的操作.
在你提到的具体例子中,有一个规范的答案.如果使makeBigger成为矩形的虚拟成员函数,则可以以适合类的方式缩放每个函数.但是,如果适用于矩形的所有(公共)方法都适用于正方形,那么这只是一个很好的OO设计.
那么让我们看看到目前为止这对你的努力有何影响:
我经常在生产代码中看到这种事情.在一个优秀的设计中修复差距是一种可行的方法,但这是不可取的.但这是一个问题,因为它导致代码在语法上是正确的,但在语义上是不正确的.它会编译并执行某些操作,但意义不正确.假设您正在迭代一个矩形向量,并将宽度缩放2,高度缩放3.这对于正方形而言在语义上毫无意义.因此它违反了"更喜欢编译时错误到运行时错误"的规则.
在这里,您考虑使用继承来重用代码.有一种说法"使用继承被重新使用,不要再使用".这意味着,您希望使用继承来确保oo代码可以在其他地方重新使用,作为其基础对象,而无需任何手动rtti.请记住,还有其他代码重用机制:在C++中,这些机制包括函数式编程和组合.
如果正方形和矩形具有共享代码(例如,基于它们具有直角的事实来计算区域),则可以通过合成(每个包含公共类)来执行此操作.在这个简单的例子中,你可能最好使用一个函数,例如:在命名空间级别提供的compute_area_for_rectangle(Shape*s){return s.GetHeight()*s.GetWidth());}.
因此,如果Square和Rectangle都继承自基类Shape,Shape具有以下公共方法:draw(),scale(),getArea()...,所有这些对于任何形状都具有语义意义,并且常见公式可以通过命名空间级别功能共享.
我想如果你稍微冥想这一点,你会发现你的第三个建议有很多瑕疵.
关于oo设计观点:正如icbytes所提到的,如果你将要有第三类,那么更有意义的是这个类是一个有意义地表达常见用途的共同基础.形状还可以.如果主要目的是绘制对象而不是Drawable可能是另一个好主意.
你表达这个想法的方式还有其他一些缺陷,这可能表明你对虚拟析构函数的误解,以及抽象的意义.每当你创建一个类的方法虚拟,以便另一个类可以覆盖它时,你应该声明析构函数是虚拟的(SM确实在Effective C++中讨论它,所以我猜你会自己发现它).这并不是抽象的.当你声明至少一个纯粹虚拟的方法时,它变成抽象的 - 即没有实现
虚拟的空虚foo()= 0; //例如,这意味着无法实例化有问题的类.显然,因为它至少有一个虚方法,所以它也应该将析构函数声明为virtual.
我希望有所帮助.请记住,继承只是可以重用代码的一种方法.良好的设计源于所有方法的最佳组合.
为了进一步阅读,我强烈推荐Sutter和Alexandrescu的"C++编码标准",特别是关于类设计和继承的部分.第34项"更喜欢继承权的构成"和37"公共继承权是可替代的.继承,不是重复使用,而是重复使用.
事实证明,更简单的解决方案是
Rectangle makeBigger(Rectangle r)
{
r.setWidth(r.width() + 10);
return r;
}
Run Code Online (Sandbox Code Playgroud)
在正方形上工作得很好,即使在这种情况下也能正确返回一个矩形.
[编辑]评论指出,真正的问题是潜在的调用setWidth.这可以用同样的方法修复:
Rectangle Rectangle::setWidth(int newWidth) const
{
Rectangle r(*this);
r.m_width = newWidth;
return r;
}
Run Code Online (Sandbox Code Playgroud)
同样,改变正方形的宽度会给你一个矩形.并且作为const节目,它为您提供了一个新的Rectangle而不更改现有的Rectangle现在,以前的功能变得更加容易:
Rectangle makeBigger(Rectangle const& r)
{
return r.setWidth(r.width() + 10);
}
Run Code Online (Sandbox Code Playgroud)