如何初始化抽象类的子类?

huangapple go评论58阅读模式
英文:

How to initialize subclasses of an abstract class?

问题

我正在尝试创建一个类似黑暗之魂风格的基于文本的游戏。我已经创建了一个抽象类,概述了黑暗之魂职业/角色类型的特征。我已经初始化了它的几种方式,但不确定哪一种更好。

一个重要的注意事项是,在所有的子类中,我只想要一个名字参数,而所有其他变量将被设置为特定于该类/角色类型的值。

以下是我创建的一个抽象类及其后续子类的示例:

class Character(ABC):
    def __init__(self, name, description, health, endurance, strength) -> None:
        self.name = name
        self.description = description
        self.health = health
        self.endurance = endurance
        self strength = strength

class Giant(Character):
    def __init__(self, name) -> None:
        super().__init__(name, description="giant", health=500, endurance=20, strength=100)

以下是第二个版本:

class Character(ABC):
    def __init__(self, name) -> None:
        self.name = name
        self.description = ''
        self.health = 0
        self.endurance = 0
        self.strength = 0

class Giant(Character):
    def __init__(self, name) -> None:
        super().__init__(name)
        self.description = "giant"
        self.health = 500
        self.endurance = 20
        self.strength = 100

哪种方式更好?还有没有完全不同的方式更好?我对继承和抽象类还相当陌生,我将不胜感激地接受您提供的任何帮助。谢谢!

英文:

I'm trying to create a dark souls style text-based game. I've created an abstract class which outlines the characteristics of a dark souls class/character type. There are a couple of ways I have initialized it and I'm not sure if one is better than the other.

One important note is that in all of the subclasses, the only parameter I want is the name parameter, while all other variables will be set equal to values specific to that class/character type.

Here is one of the abstract classes I created and its subsequent subclass:

class Character(ABC):
    def __init__(self, name, description, health, endurance, strength) -> None:
        self.name = name
        self.description = description
        self.health = health
        self.endurance = endurance
        self.strength = strength

class Giant(Character):
    def __init__(self, name) -> None:
        super().__init__(name, description="giant", health=500, endurance=20, strength=100)

Here is the 2nd version:

class Character(ABC):
    def __init__(self, name) -> None:
        self.name = name
        self.description = ''
        self.health = 0
        self.endurance = 0
        self.strength = 0

class Giant(Character):
    def __init__(self, name) -> None:
        super().__init__(name)
        self.description = "giant"
        self.health = 500
        self.endurance = 20
        self.strength = 100

Is one way better than the other? Is there a totally different way which would be better? I'm pretty new to inheritance and abstract classes and I'd appreciate any help you'd be able to provide. Thanks!

答案1

得分: 1

我确实不会使用任何一种方法,至少根据你在这个示例中描述的情况。

不同的Character子类之间唯一的不同方式是它们的统计数据不同吗?或者它们实际上会有不同的行为吗?

因为如果只是关于不同的数值,我更倾向于将Character定义为具体类而不是抽象类,然后提供特定的方法:

class Character:
  def __init__(self, name, description, 等等):
    在此处设置数值

  @classmethod
  def create_giant(cls, name):
    return cls(name=name, description="giant", health=500, 等等)

然后你可以这样创建一个巨人:

my_giant = Character.create_giant(name="Olbrzym")

对于你的两个版本,它们有略微不同的语义。在你的版本1中,调用super().__init__的人将被强制提供具体的数值,而在版本2中,他们可以依赖默认值。考虑到具有health=0的角色可能没有意义,我更倾向于版本1。

你可以看到,我的版本不使用继承。那么什么情况下会使用继承呢?当我不能轻松地通过它们的健康、耐力和力量数值来区分各种角色类型(巨人、矮人、精灵等),但实际上需要不同的行为时。

例如,如果你继续使用简单的方法,最终的代码将使用大量的构造,如:

if self.description == 'Giant':
  做巨人的事情()
elif self.description == 'Dwarf':
  做矮人的事情()
elif 等等

这是你应该使用继承而不是使用的好迹象。

编辑:

所以,要让类有不同的行为,是选择版本1还是版本2?实际上,任何一个都可以。但还有第三种方法。可能有些过于复杂,但可能会派上用场:挂钩方法。

方法是这样的:编写子类不必调用super().__init__。相反,在抽象类中编写init方法,但要调用抽象方法以填充默认值。

class Character(ABC):

  def __init__(self, name):
    self.name = name
    self.description = self._get_class_description()
    self.health = self._get_class_health()
    ...

  @abstractmethod
  def _get_class_description():
    pass

  ...其他属性也是类似的

class Giant(Character):
  def _get_class_description(self):
    return "giant"
  
  def _get_class_health(self):
    return 500

  ...

它被称为挂钩方法,因为抽象基类描述了子类应该指定的方法(挂钩),以填补行为中的空白。

英文:

I would indeed use neither approach, at least for what you've described here so far in this example.

Is the only way that different subclasses of Character are different that their stats are different? Or would they actually have different behavior?

Because if it's really only about different values, I'd instead just make Character a concrete rather than abstract class and then provide certain methods:

class Character:
  def __init__(self, name, description, and so on):
    set the values here

  @classmethod
  def create_giant(cls, name):
    return cls(name=name, description="giant", health=500, and so on)

And then you'd make a giant like so:

my_giant = Character.create_giant(name="Olbrzym")

For your versions, they have slightly different semantics. In your version 1, someone calling super().__init__ will be forced to provide concrete values, whereas in version 2, they can just rely on the default values. Given that a character with health=0 probably doesn't make sense, I'd favor version 1.

You see that my version doesn't use inheritance. When would I use inheritance? When I can't easily differentiate the various character types (Giant, Dwarf, Elf?) through their health and endurance and strength values alone but actually need different behavior.

Like, if you imagine keeping with the simple approach and you end up with code that uses a lot of constructs like

if self.description == 'Giant':
  do_giant_stuff()
elif self.description == 'Dwarf':
  do_dwarf_stuff()
elif AND SO ON

that's a good sign you should be using inheritance instead.

EDIT:

So, to have the classes different behavior, version 1 or 2? Either would work, honestly. But there's a third way. Might be overkill but might come in handy: Hook methods.

Here's how that goes: Write things so that subclasses don't have to call super().__init__. Instead, write the init in the abstract class but have it call abstract methods to fill in the default values.

class Character(ABC):

  def __init__(self, name):
    self.name = name
    self.description = self._get_class_description()
    self.health = self._get_class_health()
    ...

  @abstractmethod
  def _get_class_description():
    pass

  ... same for the other attributes

class Giant(Character):
  def _get_class_description(self):
    return "giant"
  
  def _get_class_health(self):
    return 500

  ...

It's called the hook method because the abstract base class describes the methods that a subclass should specify (the hooks) in order to fill in the gaps in behavior.

huangapple
  • 本文由 发表于 2023年2月16日 10:30:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/75467266.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定