继承出错


12

我有一些代码,其中一个好的继承模型已经走下坡路,我试图理解为什么以及如何修复它。基本上,假设您有一个Zoo层次结构,其中:

class Animal  
class Parrot : Animal 
class Elephant : Animal 
class Cow : Animal

等等

您有自己的eat(),run()等方法,一切都很好。有一天,有人来说-我们的CageBuilder类工作得很好,并使用了animal.weight()和animal.height(),但新的非洲野牛实在太强了,可以打碎墙壁,所以我要补充一下Animal类的另一个属性是isAfricanBizon(),并在选择材质时使用该属性,并且仅对AfricanBizon类覆盖它。下一个人来做类似的事情,下一件事您知道您具有所有特定于基类中层次结构子集的属性。

改善/重构此类代码的好方法是什么?这里的一种替代方法是只使用dynamic_casts来检查类型,但是这会使调用者混乱,并在整个位置添加一堆if-then-else。您可以在此处具有更多特定的接口,但是如果您仅有的话,那么基类引用也无济于事。还有其他建议吗?例子?

谢谢!


@James:那么你将不得不手工编写解析器。:S
Matteo Italia

5
显然,这是一个荒谬的客户需求案例。非洲没有野牛。您不能设计与现实无关的对象模型。除非那现实是由装满钱的人创造的。解决了问题。
汉斯·帕桑

1
吃掉所有的野牛?[我之前发布了此内容,但由于某种原因被删除了,大概是因为没有幽默感。]
James McNellis 2011年

CageBuilder是否需要自己的类?如果有一个默认的MakeCage方法,该方法可以被每个单独的类覆盖。
工作

1
您提到if-then-else混乱对于调用者是一个缺点,但是一旦调用者开始使用isAfricanBizon(),它们就会自动使用if-then-else混乱代码。因此,要么是带有isAfricanBizon()的if-then-else混乱,要么是带有动态类型转换的if-then-else混乱。
davidk01 2011年

Answers:


13

看来问题出在,而不是实现RequiresConcreteWall(),他们实现了一个名为IsAfricanBison()的标志,然后将有关是否应更改墙的逻辑移到类的范围之外。您的课程应该公开行为和要求,而不是身份。您这些类别的消费者应该根据他们所告诉的内容工作,而不是根据他们的实际情况进行工作。


1
-1:只是说什么没有做。OP已经知道这是一个坏主意,因此是一个问题。
史蒂文·埃弗斯

12

isAfricanBizon()不是通用的。假设您用过强的河豚扩展了您的动物农场,但是从isAfricanBizon()返回true来产生适当的效果只是愚蠢的。

您总是想在界面上添加回答特定问题的方法,在这种情况下,它将类似于strength()


+1:其他所有人似乎都在破坏类的概念模型(该类只是封装了各种动物的属性),以适应这种特定的用例。strength可以通过一种方法来查询material.canHold(animal),允许一种干净的方式来支持与各种材料不同的材料ConcreteWall
艾丹·库利(Aidan Cully)

与其他人对RequiresConcreteWall()的建议相比,我更喜欢strength()属性方法,因为它对于实现将来的需求更加灵活。首先,让CageBuilder类决定哪些材料足够坚固,然后您可以轻松地使用新材料扩展该类。
2011年

3

我认为您的问题是这样的:您有该库的各种客户端,这些客户端仅对层次结构的一个子集感兴趣,但是将其传递给基类的指针/引用。实际上,这就是dynamic_cast <>可以解决的问题。

客户端的设计是要尽量减少对dynamic_cast <>的使用。他们应该使用它来确定对象是否需要特殊处理,如果需要特殊处理,则对向下引用的参考进行所有操作。

如果您具有适用于几个单独的子层次结构的功能的“混合”类型集合,则可能要使用Java和C#使用的接口模式;否则,可能会使用它。具有一个纯虚拟类的虚拟基类,并使用dynamic_cast <>确定实例是否为其提供了实现。


1

您可以做的一件事是将类型的显式检查替换为isAfricanBison()您实际上对感兴趣的属性的检查,例如isTooStrong()


1
isTooStrong()代表什么?您要向动物类添加笼子特定的代码。
史蒂文·埃弗斯

1

动物不应该在乎混凝土墙。也许您可以用简单的值来表达它。

class Animal {
public:
  virtual ~Animal() {}
  virtual size_t height() const = 0;
  virtual size_t weight() const = 0;
  virtual bool isStrong() const = 0;
};

Cage *CreateCageFromSQL(Animal &a);
Cage *CreateCageFromOrangePeelsAndSticks(Animal &a);

我怀疑那是不可行的。但是,这就是玩具示例的问题。

我从不希望看到RequiresConcreteWalls()或动态指针转换的任何行。

这通常是一种廉价的解决方案。易于维护和概念化。确实,问题在于它仍然与动物类型有关。

class Animal {
public:
  virtual ~Animal() {}
  virtual CageBuilder *getCageBuilder() = 0;
};

这也不会阻止您使用共享代码,只会稍微污染Animal。

但是笼子的建造方式可能是其他系统的政策,也许每个动物都有不止一种类型的笼子建造器。您可以想出许多奇怪而复杂的组合。

我已经使用基于组件的设计来达到良好的效果,主要问题是共享Animal的所有权时可能会很麻烦。如何避免抛出析构函数是难题。

Double Dispatch是另一个选择,尽管我一直不愿介入。

除此之外,很难猜测这个问题。


0

当然,所有动物都具有的固有属性attemptEscape()。尽管有些方法可能会false在所有情况下产生结果,而另一些方法可能会基于其其他固有特性(例如size和)的启发法而获得机会weight。然后肯定会在某个时候attemptEscape()变得微不足道,因为它肯定会返回true

恐怕我还是不完全理解您的问题……所有动物都有相关的行为和特征。应该在适合的地方引入特定于该动物的动物。试图将Bison与Parrots直接关联并不是一个很好的固有设置,并且在适当的设计中实际上应该不是问题。


-1

另一种选择是使用一家为每个动物制造合适笼子的工厂。我认为如果每个条件都非常不同,这样做会更好。但是,如果仅仅是这种情况,上述RequiresConcreteWall()方法就可以做到。


-1

如何将RecommendCageType()用作RequiresConcreteWall()


-2

为什么不做这样的事情

class Animals { /***/ } class HeavyAnimals{} : Animals //The basic class for animals like the African Bison

有了HeavyAnimals类,您可以通过扩展HeavyAnimals类来创建African Bison类。

因此,现在您可以使用父类(动物)来创建其他基础类,例如HeavyAnimal类,并可以用于创建非洲野牛类和其他重型动物。因此,使用非洲野牛,您现在可以访问Animal类的方法和属性(这是所有动物的基础),并可以访问HeavyAnimals类(这是Heavy Animals的基础)


2
这可以作为混合或特质,但肯定不能作为子类。这只是在下次需要另一个属性时请求多重继承。
Ordous
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.