Python和Django中的继承与工厂函数

5 投票
2 回答
2035 浏览
提问于 2025-04-15 16:56

我正在创建一个Django应用,这个应用的模型使用了一些继承,主要是因为我需要给每个东西分配一个UUID和一个引用,以便我知道它属于哪个类。下面是一个简化版的基类:

class BaseElement(models.Model):
    uuid = models.CharField(max_length=64, editable=False, blank=True, default=lambda:unicode(uuid4()))
    objmodule = models.CharField(max_length=255, editable=False, blank=False)
    objclass = models.CharField(max_length=255, editable=False, blank=False)

class ChildElement(BaseElement):
    somefield = models.CharField(max_length=255)

我想确保objmodule、objclass和uuid能够自动设置。我从这篇文章中了解到,自己写构造函数并不是个好主意,使用工厂函数会更好。所以现在我的BaseElement和ChildElement看起来是这样的:

class BaseElement(models.Model):
    uuid = models.CharField(max_length=64, editable=False, blank=True, default=lambda:unicode(uuid4()))
    objmodule = models.CharField(max_length=255, editable=False, blank=False)
    objclass = models.CharField(max_length=255, editable=False, blank=False)

    def set_defaults(self):
        self.objmodule = unicode(self.__class__.__module__)
        self.objclass = unicode(self.__class__.__name__)
        self.uuid = unicode(uuid4())

class ChildElement(BaseElement):
    somefield = models.CharField(max_length=255)

    @staticmethod
    def create(*args, **kwargs):
        ce = ChildElement(*args, **kwargs)
        ce.set_defaults()
        return ce

这样是可以的。我可以调用ChildElement.create(somefield="foo"),然后就能得到一个合适的对象,里面的uuidobjmoduleobjclass字段都设置得很正确。然而,当我继续创建像ChildElement2ChildElement3这样的类时,我发现我在每个地方都插入了完全相同的静态工厂函数。这让我觉得不舒服,因为代码重复是个坏习惯。

如果是普通的方法,我可以把create工厂函数放到BaseElement里,但在这里我不能这样做,因为我没有self的引用(因为它还没被创建),所以无法获取调用这个方法的对象的类的信息。

有没有办法把这个工厂函数迁移到BaseElement类里,这样我就不需要到处重复这段代码,同时还能确保自动设置uuidobjmoduleobjclass的值呢?

2 个回答

2

我觉得你可以在你的BaseElement里重写save方法,这样可能会更好。然后在保存的时候,你可以设置那些字段。大概可以这样写:

class MyBase(models.Model):
    uuid = models.CharField(max_length=64, editable=False, blank=True,
        default=lambda:unicode(uuid4()))
    objmodule = models.CharField(max_length=255, editable=False, blank=False)
    objclass = models.CharField(max_length=255, editable=False, blank=False)

    def save(self):
        if not self.id:
            self.objmodule = unicode(self.__class__.__module__)
            self.objclass = unicode(self.__class__.__name__)
            self.uuid = unicode(uuid4())
        super(self.__class__.__base__, self).save()

class InheritedFromBase(MyBase):
    new_field = models.CharField(max_length=100)

我用这个方法测试了一下,似乎达到了你想要的效果。我成功创建了一个“从Base继承”的对象,里面有你需要的字段,而且没有写很多重复的代码。

7

如果你把 create() 定义成一个 @classmethod,而不是 @staticmethod,那么你就可以使用类对象,这样就不需要直接用类名来引用它了:

@classmethod
def create(cls, *args, **kwargs):
    obj = cls(*args, **kwargs)
    obj.set_defaults()
    return obj

这样做就变得通用,可以放在基类里,而不需要每个子类都写一遍。

撰写回答