Imagine I have code like this in (it's about the concept; this is example code):
# models.py
from django.db import models
class Monster(models.Model):
x = models.FloatField()
y = models.FloatField()
def use_unique_ability(self, target):
''' different monsters types will implement this differently '''
def check_attack(self, player): # should I attack this player?
''' different monsters types will implement this differently '''
class Meta:
abstract = True
class SlimyMonster(Monster):
def __init__(self):
self.attack = 3
self.hit_points = 10
def use_unique_ability(self, target):
target.hit_points -= 2 * target.attack
def check_attack(self, player): # should I attack this player?
return player.hit_points < self.hit_points
class CaerbannogRabbit(Monster):
''' well, you get the idea '''
# many more monsters
Each monster has the same attributes and method names, but different values and implementation. Monster
would probably be an interface in Java. Each monster type will override only some of the many methods.
This technically works, but it has some significant downsides:
- Each monster is a new database table with the same columns
- Because of that, I need a lot of queries to check for all monsters
- I need GenericForeignKey to reference individual monsters (instances of any of the model classes)
The other extreme isn't exactly pretty either. This is based on dynamically mixin a base class to an instance (+this) and how to import a module given the full path?:
# models.py
from django.db import models
from os.path import join
from inspect import isclass
from imp import load_source
from settings import PATH_TO_MONSTER_FILES
class InconsistentMonsterError(Exception): pass
class MonsterClass(models.Model):
name = models.CharField(max_length = 32)
attack = models.PositiveIntegerField(default = 1)
defense = models.PositiveIntegerField(default = 10)
method_file = models.CharField(max_length = 128) # it's a source file, not media file, so I'm using CharField
def __init__(self, *args, **kwargs):
super(MonsterClass, self).__init__(*args, **kwargs)
try:
modul = load_source(self.method_file, join(PATH_TO_MONSTER_FILES, self.method_file))
except IOError:
raise InconsistentMonsterError('no file named %s' % join(PATH_TO_MONSTER_FILES, self.method_file))
classes = [cls for cls in [getattr(modul, x) for x in dir(modul)] if isclass(cls)]
if not len(classes):
raise InconsistentMonsterError('file %s doesn\'t contain classes' % join(PATH_TO_MONSTER_FILES, self.method_file))
bases = self.__class__.__bases__ + (classes[-1], )
self.__class__ = type('NewClassName', bases, {
'__module__': MonsterClass.__module__,
})
class MonsterInstance(models.Model):
cls = models.ForeignKey(MonsterClass)
x = models.FloatField()
y = models.FloatField()
and for each monster a file like this:
# slimy_monster.py
class SlimyMonster(): # not a Model
def use_unique_ability(self, target):
''' different monsters types will implement this differently '''
def check_attack(self, player): # should I attack this player?
''' different monsters types will implement this differently '''
- The code seems... inellegant. It's complex, and it's like using
eval
(execute code based on an 'external' string) - Every monster must be changed in two places: the database and a file (although I suppose I could define database values in the file)
Given these two options, should I use one of them, or something in between? I tend towards the second option, but I'm not quite satisfied.