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
.