【问题标题】:Should Player inherit or own a Level?玩家应该继承还是拥有一个关卡?
【发布时间】:2013-05-10 20:25:27
【问题描述】:

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

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

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。它有一些属性,如:namehealthlevelexperience pointsdamage等。

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

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

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

所以,我认为我应该使用Level,就好像它是一个接口并从中继承Player(和其他子类),但我不确定所以我决定问你,因为我反正在这里学习。 我该怎么办?将LevelPlayer 一起使用的最正确方法是什么?

【问题讨论】:

  • 在这种情况下,听起来 Player-Level 是一种经典的“has-a”关系。你说,“而玩家有它的等级,但等级不等于等级等级。”这到底是什么意思?
  • @Santa Level-class 已经有一个名为 level 的属性。这个level 属性也是Player 应该有的属性,它只是一个整数来告诉我们的玩家处于哪个级别。 Player 也有经验值(exp),这也是Level 的一个属性。这就是为什么从 Level 继承 Player 是有意义的,否则我们会调用 Player.level.level 来获取玩家级别的实际数值。
  • 确实,player.level.level有点别扭。人们会期望该级别应该只是一个整数,而不是一个完整的类。这个“级别”的代码写在哪里,你能改变它吗?

标签: python inheritance relationship class-hierarchy


【解决方案1】:

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

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

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

这应该已经敲响了一些警钟,但这里有一个提示:这与拥有一个 Name-class 相同,其中包含一个名为 namestring-type 属性。

所以答案是: 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 设为属性)

【讨论】:

  • 再次感谢您的回答,我只是被 is-a 和 has-a 关系所困扰,既不合适,也没有意识到问题出在我的课堂上 :) 我仍然不是exp 不是玩家的属性,但我认为它是十进制数字时它适合,也谢谢你。而且我正在使用player.level.increase() 来增加玩家的技能点(每个级别都会获得一个),因为他的级别增加了,但我刚刚意识到我应该改用它来引发事件level_up 并订阅玩家的add_skill_point 到它... :)
  • @SkamahOne 很高兴帮助任何真正愿意学习的人 :) 对这次活动有很好的想法,尽管我会给他们起不同的名字。正如您在问题中提到的那样,@ 987654346@ 事件不会比@ 987654347@ 方法更有意义。而是称它为Level.increase,因为这是事件发生,级别增加。现在这是可选的,但不是订阅add_skill_point,而是创建一个新方法Player.on_level_up() 并将其订阅到Level.increase。但这取决于您的活动系统和您的个人喜好。
【解决方案2】:

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

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

【讨论】:

  • 在询问面向对象的风格时,还要抽象地考虑一下什么设计对您的应用程序有意义——在关注现有资源如何组合在一起之前。首先,清楚地看到玩家应该拥有一个关卡,然后弄清楚如何适当地安装这些棋子。就像python的禅一样; “应该有一种——最好只有一种——显而易见的方法。”
  • 即使我没有接受你,我完全给你一个好的答案! :)
【解决方案3】:

如果您已经确定 Player 不是关卡,那么继承不是正确的机制。继承适用于“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:

【讨论】:

    【解决方案4】:

    玩家肯定不是等级,但他有等级。我看不出你在使用上面定义的玩家类中的关卡类有什么问题,因为它会跟踪作为关卡一部分的玩家的所有重要信息。

    【讨论】:

      【解决方案5】:

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

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

      例如,

      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
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-10-28
        • 2016-09-02
        • 2018-06-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-02-24
        • 1970-01-01
        相关资源
        最近更新 更多