The actual code is vastly different and on a whole different topic, but I felt that this small example might be better since my problem is understanding the key concepts for complex inheritance scenarios (and not my specific domain).
Let's consider we have a basic Entity class:
from enum import Enum
from abc import abstractmethod
class Condition(Enum):
ALIVE = 1
DEAD = 2
UNDEAD = 3
class Entity(object):
def __init__(self):
self.condition = Condition.ALIVE
self.position = 0
self.hitpoints = 100
def move(self):
self.position += 1
def changeHitpoints(self, amount):
self.hitpoints += amount
@abstractmethod
def attack(self, otherEntity):
pass
This is a base class other concrete entities inherit from and attack() has to be abstract, since every entity should implement its own style of attack method.
Now we could implement some entities:
class Orc(Entity):
def __init__(self):
super().__init__()
self.hitpoints = 150
self.damage = 10
def attack(self, otherEntity : Entity):
otherEntity.changeHitpoints(-self.damage)
class Human(Entity):
def __init__(self):
super().__init__()
self.damage = 8
def attack(self, otherEntity : Entity):
otherEntity.changeHitpoints(-self.damage)
class Undead(Entity):
def __init__(self):
super().__init__()
self.condition = Condition.UNDEAD
self.damage = 5
def attack(self, otherEntity : Entity):
# harm enemy
otherEntity.changeHitpoints(-self.damage)
# heal yourself
self.changeHitpoints(1)
This works fine. However, I am struggling to figure out a good solution (DRY-style) for implementing "abilities" and other stuff.
For example, if Orc and Human should not only move, but also be able to jump, it would be interesting to have something like:
class CanJump(Entity):
def jump(self):
self.position += 2
class Orc(Entity, CanJump):
(...)
class Human(Entity, CanJump):
(...)
This introduces two problems. (1) we need access to self.position in CanJump, thus we have to inherit from Entity?! If we do so, we have to implement the abstract method attack() in class CanJump. This does not make sense, since CanJump should just give entities the ability of a new type of movement. (2) in the future we might want to implement for example a decorator that checks if the condition of an entity is Condition.DEAD before executing move(), attack(), ... This also means CanJump needs access to self.condition.
What would be a clean solution for this type of problems?
What if there is a need for further subclassing? E.g. we might be interested in creating an UndeadHuman like class UndeadHuman(Undead, Human). Due to the linearization (Undead first in order) it should have the attack behavior of an Undead but it also needs the CanJump from a Human.