How to initialize subclasses of an abstract class?

Question:

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!

Asked By: propserouspi404

||

Answers:

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.

Answered By: cadolphs