让我们说我们有一个具体的class Apple
.(可以实例化Apple对象.)现在,有人来自class Peach
Apple 并从Apple中获取摘要.它是抽象的,因为它引入了一个新的纯虚函数.Peach的用户现在被迫从中派生并定义这个新功能.这是一种常见的模式吗?这是正确的吗?
样品:
class Apple { public: virtual void MakePie(); // more stuff here };现在让我们说我们有一个具体的class Peach : public Apple { public: virtual void MakeDeliciousDesserts() = 0; // more stuff here };
class Berry
.有人class Tomato
从贝瑞那里得到一个摘要.它是抽象的,因为它覆盖了Berry的虚拟函数之一,并使其成为纯虚拟函数.Tomato的用户必须重新实现之前在Berry中定义的功能.这是一种常见的模式吗?这是正确的吗?
样品:
class Berry { public: virtual void EatYummyPie(); // more stuff here };注意:名称是人为的,并不反映任何实际代码(希望如此).在撰写这个问题时,没有任何成果受到伤害.class Tomato : public Berry { public: virtual void EatYummyPie() = 0; // more stuff here };
来自Apple的Re Peach:
如果Apple是一个值类(即具有复制ctor,不相同的实例可以相等,等等),请不要这样做.有关原因,请参阅Meyers更有效的C++项目33.
如果Apple具有公共非虚拟析构函数,请不要这样做,否则当用户通过指向Peach的指针删除Apple时,会引发未定义的行为.
否则,你可能很安全,因为你没有违反Liskov的可替代性.桃子IS-A Apple.
如果您拥有Apple代码,则更愿意分解出一个共同的抽象基类(也许是Fruit)并从中派生出Apple和Peach.
来自Berry的Re Tomato:
与上述相同,加上:
避免,因为它是不寻常的
如果必须,请记录番茄必须做的派生类,以免违反Liskov替代性.你在Berry中重写的功能 - 让我们称之为Juice()
- 强加某些要求并作出某些承诺.派生类的实现Juice()
必须不再需要,并且不会少.然后DerivedTomato IS-A Berry和只知道Berry的代码是安全的.
可能,您将通过记录DerivedTomatoes必须调用来满足最后的要求Berry::Juice()
.如果是这样,请考虑使用模板方法:
class Tomato : public Berry { public: void Juice() { PrepareJuice(); Berry::Juice(); } virtual void PrepareJuice() = 0; };
现在,番茄IS-A浆果很有可能与植物学期望相反.(例外情况是派生类的实现会PrepareJuice
施加超出其强加的额外前提条件Berry::Juice
).
在我看来,这似乎是一个糟糕的设计的迹象.如果你想从一个封闭的库中获取一个具体的定义并扩展它并从中分出一堆东西,可能会被强制,但那时我会认真考虑关于Encapsulation over Inheritance的指导原则.如果你可以封装的话,你可能应该.
是的,我想的越多,这是一个非常糟糕的主意.