继承和多态性 - 易用性 vs 纯度

在一个项目中,我们的团队正在使用对象列表对所有应该以类似方式处理的数据集执行大量操作。特别是,理想情况下,不同的对象将具有相同的作用,这可以通过多态性很容易地实现。我遇到的问题是继承意味着 is a 关系,而不是 has 关系。例如,几个对象有一个伤害计数器,但为了在对象列表中易于使用,可以使用多态性——除非这意味着是一个关系不会是真的。 (一个人不是伤害计数器。)

我能想到的唯一解决方案是让类的成员在隐式转换而不是依赖继承时返回正确的对象类型。放弃 is a / has 理想以换取易于编程会更好吗?

编辑: 更具体地说,我使用的是 C ,因此使用多态将允许不同的对象“表现相同”,因为派生类可以驻留在单个列表中并由基类的虚函数操作。使用接口(或通过继承模仿它们)似乎是我愿意使用的解决方案。

请先 登录 后评论

9 个回答

0124816

我同意 Jon 的观点,但假设你仍然需要一个单独的伤害计数器类,你可以这样做:

class IDamageable {
  virtual DamageCounter* damage_counter() = 0;
};
class DamageCounter {
  ...
};

每个可损坏的类都需要提供自己的 damage_counter() 成员函数。这样做的缺点是它为每个可损坏的类创建了一个 vtable。您可以改为使用:

class Damageable {
 public:
  DamageCounter damage_counter() { return damage_counter_; }
 private:
  DamageCounter damage_counter_;
};

但是当多个父对象具有成员变量时,许多人不酷具有多重继承。

请先 登录 后评论
Derek Park

有时为了现实放弃理想是值得的。如果它会导致一个大问题“正确地做”而没有真正的好处,那么我会做错。话虽如此,我经常认为花时间做正确的事情是值得的,因为不必要的多重继承会增加复杂性,并且它可能导致系统的可维护性降低。你真的必须决定什么最适合你的情况。

一种选择是让这些对象实现一个 Damageable 接口,而不是从 DamageCounter 继承。这样,一个人有-一个伤害计数器,但可损坏的。 (我经常发现接口作为形容词比作为名词更有意义。)然后你可以在 Damageable 对象上有一个一致的损坏接口,而不是暴露一个损坏计数器是底层实现(除非你需要到)。

如果你想走模板路线(假设是 C 或类似的),你可以用 mixin 来做到这一点,但如果做得不好,这会很快变得丑陋。

请先 登录 后评论
Brian

这可以使用多重继承来实现。在您的特定情况 (C) 中,您可以使用纯虚拟类作为接口。这允许您拥有多重继承而不会产生范围/歧义问题。示例:

class Damage {
    virtual void addDamage(int d) = 0;
    virtual int getDamage() = 0;
};

class Person : public virtual Damage {
    void addDamage(int d) {
        // ...
        damage += d * 2;
    }

    int getDamage() {
        return damage;
    }
};

class Car : public virtual Damage {
    void addDamage(int d) {
        // ...
        damage += d;
    }

    int getDamage() {
        return damage;
    }
};

现在Person 和Car 都是'is-a' Damage,也就是说,它们实现了Damage 接口。使用纯虚拟类(这样它们就像接口一样)是关键,应该经常使用。它将未来的变化与改变整个系统隔离开来。阅读开闭原则以了解更多信息。

请先 登录 后评论
Derek Park

@安德鲁

<块引用>

“行为相同”的理想和多态性绝对无关。多态是如何轻松实现的?

例如,它们都有一个共同的功能。我们称之为addDamage()。如果你想做这样的事情:

foreach (obj in mylist)
    obj.addDamage(1)

那么您要么需要一种动态语言,要么需要它们从公共父类(或接口)进行扩展。例如:

class Person : DamageCounter {}
class Car : DamageCounter {}

foreach (DamageCounter d in mylist)
    d.addDamage(1)

然后,您可以在某些非常有用的情况下将 PersonCar 视为相同。

请先 登录 后评论
Jon Limjap

我认为您应该实现接口以能够强制您的具有关系(我在 C 中这样做

请先 登录 后评论
Steven A. Lowe

多态不需要继承。多态是当多个对象实现相同的消息签名(方法)时得到的。

请先 登录 后评论
Andrew

从长远来看,“做对了”会有好处,只是因为后来维护系统的人会发现如果一开始就做对了会更容易理解。

根据语言的不同,您可能可以选择多重继承,但通常简单的接口最有意义。 “简单”是指制作一个不会太过分的界面。最好有很多简单的接口和一些单一的接口。当然,总有一个取舍,接口太多可能会导致“忘记”……

请先 登录 后评论
Derek Park

@凯文

<块引用>

通常,当我们谈论“is a”与“has a”时,我们谈论的是继承与组合。

嗯...损坏计数器只是您的派生类之一的属性,不会就您的问题以“一个人是损坏计数器”的形式进行讨论。

将伤害指示物作为一个属性不允许他将带有伤害指示物的不同对象整合到一个集合中。例如,一个人和一辆车可能都有损坏计数器,但你不能有 vector<Person|Car>vector<with::getDamage()> 或大多数语言中的任何类似的东西。如果你有一个公共的 Object 基类,那么你可以用这种方式推送它们,但是你不能一般地访问 getDamage() 方法。

当我读到它时,这就是他问题的本质。 “为了将某些对象视为相同的对象,即使它们不同,我是否应该违反 is-ahas-a?”

请先 登录 后评论
Andrew Grant

这个问题真的很令人困惑:/

您的粗体问题非常开放,答案是“视情况而定”,但您的示例并未真正提供有关您所询问上下文的太多信息。这些行让我感到困惑;

<块引用> <块引用>

应该以类似方式处理的数据集

什么方法?这些集合是否由函数处理?又一个班?通过数据上的虚函数?

<块引用> <块引用>

特别是,理想情况下,不同的对象应该具有相同的行为,这很容易通过多态实现

“行为相同”的理想和多态性绝对无关。多态是如何轻松实现的?

请先 登录 后评论