Django模型:如何兼顾复制模型与子类化的优势?
Django角色模型设计:兼顾无冗余与查询隔离
我有一个Django基类模型Character,代码如下:
class Character(models.Model): strength = models.blabla dex = models.blabla ...
该模型已配置好属性并接入Admin后台,运行正常。
接下来需要创建NonPlayerCharacter模型,它要包含Character的所有属性,再新增一两个字段。我考虑了两种实现方式,各有优劣:
方案1:复制模型并添加字段
最直接的方式是复制Character的代码并新增字段:
class NonPlayerCharacter(models.Model): strength = models.blabla dex = models.blabla ... faction = models.blabla
- 优点:实现简单,后续若需对不同模型做差异化调整也很方便。
- 缺点:存在严重代码冗余,通用属性或方法的修改需要同步到多个模型。如果后续新增Mob等其他角色类型,维护成本会大幅增加。
方案2:子类化Character
通过继承Character来扩展:
class NonPlayerCharacter(Character): faction = models.blabla
- 优点:无需同步多模型的通用修改,每个模型只需关注自身新增的属性和方法。
- 缺点:默认查询
Character.objects时会返回所有子类实例(比如NonPlayerCharacter)。虽然可以每次查询时手动过滤,但我希望Character.objects仅返回纯Character实例,子类实例由各自的模型管理器查询获取。
推荐解决方法
方法1:使用抽象基类(最推荐)
把公共字段抽离到抽象基类中,让Character和NonPlayerCharacter分别继承这个抽象类。这样两个模型是完全独立的数据表,天然隔离查询,同时避免代码冗余。
代码示例:
class BaseCharacter(models.Model): # 所有角色的公共字段 strength = models.IntegerField() dex = models.IntegerField() # 其他公共属性... class Meta: abstract = True # 标记为抽象基类,不会生成数据表 class Character(BaseCharacter): # 玩家角色专属字段(如果没有就留空) player_name = models.CharField(max_length=50) class NonPlayerCharacter(BaseCharacter): # NPC专属字段 faction = models.CharField(max_length=100) ai_behavior = models.CharField(max_length=50)
- 优势:
- 公共字段统一维护,彻底消除冗余
Character.objects仅返回玩家角色,NonPlayerCharacter.objects仅返回NPC,查询完全隔离- 新增角色类型(如Mob)只需继承
BaseCharacter并添加专属字段,维护成本极低
方法2:给父类添加自定义管理器(保留多表继承)
如果需要保留父类与子类的关联关系(比如通过父类实例反向获取子类数据),可以给Character添加自定义管理器,自动过滤掉属于子类的实例。
Django多表继承会在父类表中自动生成子类的关联字段(如nonplayercharacter_ptr_id),如果该字段不为空,说明这个实例属于子类。利用这一点实现过滤:
from django.db import models class CharacterManager(models.Manager): def get_queryset(self): queryset = super().get_queryset() # 遍历所有子类,过滤掉存在关联ptr的实例 for subclass in Character.__subclasses__(): ptr_field = f"{subclass._meta.model_name}_ptr_id" queryset = queryset.filter(**{ptr_field: None}) return queryset class Character(models.Model): strength = models.IntegerField() dex = models.IntegerField() # 其他公共字段... objects = CharacterManager() # 使用自定义管理器 class NonPlayerCharacter(Character): faction = models.CharField(max_length=100)
- 优势:
- 保留多表继承的关联能力,可通过
character.nonplayercharacter获取子类实例(如果存在) Character.objects自动返回纯父类实例,无需手动过滤- 公共字段集中维护,无冗余
- 保留多表继承的关联能力,可通过
- 注意:如果子类定义在其他App中,要确保该App已被Django加载,否则
__subclasses__()无法识别到子类。
内容的提问来源于stack exchange,提问作者Josh
相关产品推荐
相关产品推荐

