1

在过去的几周里,我一直在尽可能多地学习 OOP,并且我学到了很多东西,但是我不确定这一点,我的类层次结构应该是什么样的?

想象两个类,这是一个Level类:

class Level(object):
    def __init__(self, level, exp):
        self.level = level
        self.exp = exp

    @property
    def required_exp(self, level=None):
        return 80 + 40 * (level or self.level)

    def add_exp(self, exp):
        self.exp += exp
        while self.exp >= self.required_exp:
            self.exp -= self.required_exp
            self.level += 1

显然它也有其他一些方法,但这是基本思想。

现在调用第二类Player。它具有一些属性,例如:name, health, level, experience points, damage, 等。

到目前为止,我所学到的(SO 上有人这么说)是在我不确定是使用继承(is-a-relationship)还是属性(has-a-relationship)时问自己一个问题。问题是:玩家是一个级别,还是玩家有一个级别?

嗯……两者都不是。玩家肯定不是一个级别,我敢肯定。玩家有它的等级,但等级不等于Level等级。相反,玩家有等级和经验值,它们都是Level类的属性。您还可以将经验值添加到 Player,因此从Level.

作为旁注,这些命名对我来说也没有任何意义。有类似的东西是有意义的:Level.increase(self)将 Level 增加一。然而,对于玩家来说,调用 会更有意义Player.level_up(self),因为大多数(所有?)rpg 游戏都使用。调用Player.increase(self)没有任何意义,你不能增加Player一级,你正在增加它的级别。话又说回来,打电话Level.level_up(self)也没有多大意义......

所以,我认为我应该像使用Level接口一样使用它并从中继承Player(和其他子类),但我不确定所以我决定问你,因为无论如何我是来学习的。我应该怎么办?什么是最合适的使用Level方式Player

4

5 回答 5

4

我相信是我告诉你问自己这个问题的人:“A 类是 B 类,还是 A 类有 B 类?” ,所以我觉得我必须回答这个问题。

我不能 100% 确定你的关卡实际上是做什么的,但我假设它与口袋妖怪、魔兽世界、英雄联盟和其他数千款游戏中使用的关卡相似。如果是这样,你的 -class 已经出错了Level,它不应该是这样的。

你肯定在一件事上是对的; “玩家当然不是一个级别” 正如其他人已经提到的,您应该使用has-a关系。玩家确实有水平,我们都知道。但是,您告诉我 player 没有 type 的属性Level,而是一个整数-attribute 来保存您的-classlevel的数值。Level

这应该已经敲响了一些警钟,但这里有一个提示:这与拥有一个在其中调用 -type 属性的Name-class相同。stringname

所以答案是: Name类本身应该已经是一个字符串,它应该继承自string,而不是在其中有一个string属性来保存它的值。这同样适用于你的Level,它本身已经是一个整数,所以从继承它int,然后添加exp它的方法。

现在您可以使用player.level返回玩家等级的整数值。要获得exp,您“必须”使用player.level.exp. 我引用了“必须”,因为即使听起来很奇怪,它exp也是关卡的一个属性。它与数字的小数点基本相同,有意义吗?

此外,现在调用类似的东西player.level.increase()更有意义。虽然,它已经是一个整数值,为什么不直接做player.level += 1呢?如果您在添加中需要一些高级的东西,您可以重写该Level.__add__(self, value)方法。我想不出一个你为什么不需要这个的单一原因?(不是现在你已经创建了required_exp一个属性)

于 2013-05-10T20:46:02.110 回答
3

玩家有一个级别。正如你所说,“玩家肯定不是一个级别”。当您使用继承时,它意味着玩家是关卡的子类型(当然这根本没有意义)。

这是一个明显的has-a关系。使用诸如player1.levelup()player1.addxp()引用级别属性的方法与级别交互。

于 2013-05-10T20:35:33.863 回答
2

如果您已经确定 Player 不是 Level,那么继承不是正确的机制。继承适用于“A car is a vehicle, and a motorbike is a vehicle”之类的关系,因此在这种情况下,Car 和 Motorbike 都是 Vehicle 的子类。

但是,这种关系也不适合真正的composition,因为 Level 不应该被表示为另一个具有自己功能的类。玩家确实有一个级别,但是正如您所指出的,player.level_up()并且player.add_experience()比使用level对象调用这些方法更清楚。这是因为与玩家关卡的交互通过其方法而不是通过player.level.level. 然后,您应该考虑将 Level 类的功能移至 Player:

类图

于 2013-05-10T20:54:59.550 回答
0

球员绝对不是一个水平,但他有一个水平。我没有看到您在使用上面定义的玩家类中的关卡类时遇到问题,因为它会跟踪作为关卡一部分的玩家的所有重要信息。

于 2013-05-10T20:41:32.653 回答
0

您可以简单地跟踪总经验,而不是同时跟踪关卡和与该关卡相关的额外经验。

从总经验中,您可以得出达到下一个级别所需的级别和经验数量,或者您可能感兴趣的任何其他此类数量。

例如,

class Player(object):
    def __init__(self, exp=0):
        self.exp = exp
    @property
    def level(self):
        if self.exp<80:
            return 0
        return (self.exp-80)//40 + 1
    @property
    def required_exp(self):
        return 80 + 40*self.level
    def add_exp(self, exp):
        self.exp += exp


ringo = Player(exp=80)
print(ringo.level)
# 1

print(ringo.required_exp)
# 120
于 2013-05-10T21:48:36.430 回答