6

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

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

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

4

10 回答 10

5

我认为您应该实现接口以便能够强制执行您关系(我在 C# 中这样做):

public interface IDamageable
{
    void AddDamage(int i);
    int DamageCount {get;}
}

你可以在你的对象中实现它:

public class Person : IDamageable

public class House : IDamageable

并且您可以确定 DamageCount 属性并有一种方法可以让您增加损坏,而不会暗示人和房屋在某种层次结构中相互关联。

于 2008-08-15T01:54:52.993 回答
3

这可以使用多重继承来完成。在您的特定情况下(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 接口。使用纯虚拟类(因此它们就像接口)是关键,应该经常使用。它将未来的变化与改变整个系统隔离开来。阅读开闭原则以获取更多信息。

于 2008-08-15T03:41:05.930 回答
1

我同意 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_;
};

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

于 2008-09-16T09:56:36.310 回答
0

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

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

如果你想走模板路线(假设 C++ 或类似的),你可以用 mixins 来做这件事,但如果做得不好,那会很快变得丑陋。

于 2008-08-15T01:13:01.660 回答
0

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

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

看到这个:

http://www.artima.com/designtechniques/compoinh.html

这可能会帮助你一路走来。

@ Derek:从措辞来看,我认为有一个基本条件,重新阅读了我现在有点明白他在说什么的问题。

于 2008-08-15T01:15:32.417 回答
0

这个问题真的很混乱:/

您的粗体问题非常开放,并且答案是“取决于”,但是您的示例并没有真正提供有关您所询问的上下文的太多信息。这些台词让我感到困惑;

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

有什么办法?集合是否由函数处理?另一个班级?通过数据上的虚函数?

特别是,不同的对象在理想情况下会表现相同,这很容易通过多态性实现

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

于 2008-08-15T01:17:13.803 回答
0

@凯文

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

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

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

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

于 2008-08-15T01:22:11.953 回答
0

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

根据语言的不同,您可能会选择多重继承,但通常简单的接口最有意义。我所说的“简单”是指制作一个不想要太多的界面。最好有很多简单的接口和一些单一的接口。当然,总有一个权衡,太多的接口可能会导致那些被“遗忘”...

于 2008-08-15T01:29:00.770 回答
0

@安德鲁

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

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

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

然后您需要一种动态语言,或者您需要它们从一个公共父类(或接口)扩展。例如:

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

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

然后,您可以在某些非常有用的情况下进行相同的Person处理。Car

于 2008-08-15T01:29:19.193 回答
0

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

于 2010-01-18T21:29:48.753 回答