Python API 设计模式问题

9

我经常发现我有类实例,这些实例是其他类实例的后代,呈树状结构。例如,假设我正在使用Python制作CMS平台。我可能有一个领域(Realm),在其下面是一个博客(Blog),再在其下是一篇文章(Post)。每个构造函数都将其父对象作为第一个参数,以便知道它属于哪个对象。它可能看起来像这样:

class Realm(object):
    def __init__(self, username, password)

class Blog(object):
    def __init__(self, realm, name)

class Post(object);
    def __init__(self, blog, title, body)

我通常会在父类中添加一个“create”方法,以便链接更加自动化。我的Realm类可能看起来像这样:

class Realm(object):
    def __init__(self, username, password):
        ...
    def createBlog(self, name):
        return Blog(self, name)

这使得API用户不必导入每个模块,只需导入顶层模块。可能是这样的:

realm = Realm("admin", "FDS$#%")
blog = realm.createBlog("Kittens!")
post = blog.createPost("Cute kitten", "Some HTML blah blah")

问题在于这些创建方法是冗余的,我必须在两个地方都pydoc相同的参数。
我想知道是否有一种模式(可能使用元类)将一个类实例连接到父类实例。某种方式使我能够调用像这样的代码,并让博客知道它的父域是什么:
realm = Realm("admin", "FDS$#%")
blog = realm.Blog("Kittens!")

你的意思是这些实例彼此之间有继承关系。一开始我还以为你在重新发明面向对象编程呢。 - Fred Foo
你说得对,我更新了问题。 - jpsimons
2个回答

1
您可以为具有add()方法的容器使用一个公共基类。
class Container(object):
    def __init__(self, parent=None):
        self.children = []
        self.parent = parent
    def add(self, child)
        child.parent = self
        self.children.append(child)
        return child

在派生类中使parent参数变为可选项。

class Blog(Container):
    def __init__(self, name, realm=None):
        Container.__init__(realm)
        self.name = name

你现在的代码应该是这样的

realm = Realm("admin", "FDS$#%")
blog = realm.add(Blog("Kittens!"))
post = blog.add(Post("Cute kitten", "Some HTML blah blah"))

您将不再拥有任何create...()方法,因此无需重复记录任何内容。

如果设置父级涉及到更多操作而不仅仅是修改parent属性,则可以使用属性或setter方法。

编辑:正如您在下面的评论中指出的那样,子项应该在构造函数结束时与父项绑定。可以修改上述方法以支持此操作:

class Container(object):
    def __init__(self, parent=None):
        self.children = []
        self.parent = None
    def add(self, cls, *args)
        child = cls(self, *args)
        self.children.append(child)
        return child

class Realm(Container):
    def __init__(self, username, password):
        ...

class Blog(Container):
    def __init__(self, realm, name):
        ...

class Post(Container):
    def __init__(self, blog, title, body):
        ...

realm = Realm("admin", "FDS$#%")
blog = realm.add(Blog, "Kittens!")
post = blog.add(Post, "Cute kitten", "Some HTML blah blah")

可以这样做,但我在考虑一种方法,在构造函数运行结束时将它们联系起来。这样,即使是暂时的,Blog 也不能存在而没有父级(也许会引发异常,如“不能直接构造此对象,请从父级上下文中构造”或类似的内容)。 - jpsimons
你的 add 方法需要在此处返回子项。目前,add 方法没有返回任何内容,因此行 blog = realm.add(..) 将导致 blog == None。 - randlet
@darkporter:已经编辑了我的答案以反映这一点。 - Sven Marnach
嗯,我喜欢这个。它提供了双向链接,因此您可以迭代您的子元素。让我试试玩。并且答案被接受。 - jpsimons
在这种情况下,我会从构造函数参数中删除父级。只会有一个通用的“parent”属性,我会检查它。 - jpsimons
算了,那个方法行不通。但是如果按照惯例第一个参数是父级,保持你现在的写法就可以。 - jpsimons

0

这样怎么样,只需对其进行子类化。在我的 Realm 构造函数中:

class Realm(object):
    def __init__(self, username, password):
        ...
        parent = self
        original_constructor = blog.Blog.__init__
        class ScopedBlog(blog.Blog):
            def __init__(self, *args):
                self.parent = parent
                original_constructor(self, *args)
        self.Blog = ScopedBlog

看起来可以工作。并且可以使用基类或元类进行泛化。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接