2

I'm working on a server implementation for a large game with many gametypes. There are several kinds of interactable entities: players, monsters, objects, vehicles.

All entities share the same base class (which is Cython):

cdef class Entity:
    cdef public int id
    cdef public double x,y,z,yaw,pitch,speed

    def __init__(self, int id, double x, double y, double z, double yaw, double pitch):
        self.id = id
        self.x = x
        self.y = y
        self.z = z
        self.yaw = yaw
        self.pitch = pitch
        self.speed = 0.1

Then there's a subclass for each of the earlier mentioned entity types, these subclasses implement the different packets involved for each of those types.

All entities support position and rotation, and all entities can in theory support different behaviors (and there can be quite a lot depending on the gametype) but often do not need them. There could be around ~90 different behaviors, so supporting all of that on the base MonsterEntity/whatever class doesn't make sense. A lot of those behaviors depend on stuff like health though, so if I don't implement that in a base class I'd have to implement it in a subclass.. but there are so many behaviors like that that are related I'd end up with either a lot of repeated code in different subclasses or thousands of usually unnecessary lines in a single big class.

I decided some kind of composition would make a lot more sense here, so I've tried using multiple inheritance since Python handles it pretty well:

class EntityHealth(object):
    def __init__(self, maxhealth=20):
        self.health = maxhealth
        self.maxhealth = maxhealth

    def damage(self, amount):
        # damage the entity
        pass

    def setHealth(self, health):
        # set the entity's health
        pass

class EntityInventory(object):
    def __init__(self):
        self.inventory = Inventory()

    def dropAllItems(self):
        # do stuff
        pass

class SlayableMonster(Monster, EntityHealth, EntityInventory):
    def __init__(self, id, name, x, y, z, yaw, pitch):
        Monster.__init__(self, id, name, x, y, z, yaw, pitch, meta, uuid, skinblob)
        EntityHealth.__init__(self, 20)
        EntityInventory.__init__(self)

I've heard people argue multiple inheritance should never be used, but this seems like a very concise design.

Some thoughts...

  • All "behavior" classes would inherit from object or one behavior only for extending that behavior.
  • The diamond problem shouldn't ever happen as a result because you would never have two classes that inherit from the same class up the tree both applied to the same entity.

So MRO problems should be impossible because the design is simple, there is no complex inheritance hierarchy.

Is this the best solution here, or is there something equally/more concise with the same advantages? I just want to isolate relevant code together while being able to pick which entities need that kind of behavior.

2

Multiple inheritance is sometimes the right thing to do.

You probably shouldn't inherit both from Car and OilRig, but inheriting from both Walker and Talker can make a lot of sense, particularly if using delegation would introduce a lot of trivial delegating methods that are a pain to maintain. It is particularly benign if it verges on emulating traits or implementing multiple interfaces with default implementations.

It looks as if that is exactly what you are doing here. Certainly you don't have any of the issues (selection of indirectly inherited virtual methods in the diamond configuration, undefined internal layout of derived objects) that caused so many problems in C++ and influenced the conventional wisdom that you should never do it.

1

The diamond problem shouldn't ever happen as a result because you would never have two classes that inherit from the same class up the tree both applied to the same entity.

Never say never. I bet you within all those 90 behaviors, you will have a diamond problem. Even worse if you don't notice it.

From the small example, it seems you actually want mixins. This way, the behavior would be a field within the subclass and the subclass would implement an interface of that behavior with implementation simply delegation to the instance within the field. This would be too verbose if the language doesn't have explicit support for this, but can be good replacement for multiple inheritance otherwise.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.