能否提供一个简单的Python示例来解释“好莱坞原则”/控制反转?

2 投票
3 回答
1766 浏览
提问于 2025-04-16 19:47

我对Twisted这个网络框架非常感兴趣。根据我所了解,这个框架使用了“好莱坞原则”。我只知道这个词,但对这个设计模式完全不懂。 我在网上搜索了很多关于好莱坞原则在Python中实现的内容,但结果很少。 有没有人能给我一些简单的Python代码,来说明这个设计模式呢?

3 个回答

2

“好莱坞原则”这个名字来源于好莱坞电影行业。在这个行业里,紧张的第一次试镜演员常常会在试镜后不停地打电话给制片公司,询问自己是否获得了角色。在软件开发中,我们把这种行为称为轮询,这种做法非常低效。在好莱坞的比喻中,这不仅浪费了成功申请者的时间(因为他们可能在选角之前就反复打电话),也浪费了制片公司的时间(因为很多未被选中的申请者仍然会打电话询问)。

下面是一个Python文件,希望能让你更好地理解这个原则是如何运作的:

首先,我们有一个Actor(演员),他们可以进行试镜,或者获得角色:

class Actor(object):
    def __init__(self, name):
        self.name = name

    def youGotThePart(self):
        print self.name, "got the part."

    def perform(self):
        print self.name, "performs an audition."

接下来,我们有选角的过程:

applicants = []
def audition(actor):
    actor.perform()
    applicants.append(actor)

def cast():
    import random
    selection = random.choice(applicants)
    selection.youGotThePart()

试镜会要求演员表演,当选角进行时,会随机选择一位演员(作为一个不参与好莱坞过程的人,我觉得这可能是最真实的模拟)。然后,这位演员会收到通知(制片公司“打电话”给他们)。

最后,这就是整个选角过程:

alice = Actor("alice")
bob = Actor("bob")
carol = Actor("carol")
dave = Actor("dave")

audition(alice)
audition(bob)
audition(carol)
audition(dave)

cast()

如你所见,这个过程非常简单,不涉及图形界面、网络或其他外部输入源。它只是一个组织代码的方式,以避免无谓的检查和重复检查同样的状态。如果你考虑将这个程序结构设计成遵循这个原则,你可能会有一个类似这样的循环:

while True:
    for actor in alice, bob, carol, dave:
        if actor.didIGetThePart():
            break
    maybeGiveSomeoneThePart()

这当然是非常浪费的,因为谁知道演员们可能会在制片公司做出选择之前打多少次电话呢?

2

Twisted 是一个采用异步方式设计的框架。你的程序会触发某个事件,但它不会等这个事件完成,而是把控制权交还给 Twisted 的事件循环(在 Twisted 里叫做“反应器”)。当反应器发现有事情需要你处理时,它会用你之前提供的“回调”函数来调用你的程序。基本的工作流程是这样的:

something_deferred = make.twisted.do.something()

这里的 something_deferred 通常是一个 twisted.internet.defer.Deferred 实例,代表 something() 的结果。一般来说,something() 需要一些时间才能完成,所以这个 deferred 对象在结果还没出来的时候就已经返回了。接下来,你需要定义一个函数,让 Twisted 在结果准备好时调用它。

def something_callback(result):
    do_something_else(result)

在大多数情况下,你还应该定义一个函数,用来处理 something() 出现错误的情况。

def something_errback(fault):
    cleanup_something()

最后,你需要告诉 Twisted,当 something() 最终准备好时,使用这些函数。

something_deferred.addCallbacks(something_callback, something_errback)

注意,你并不是自己去调用这些函数,而是把它们的名字传给 addCallbacks()。Twisted 会负责调用你的函数。

并不是每个例子都严格遵循这个模式,但在大多数情况下,你会在一个实现了 Twisted 定义接口的类中放一个实例方法,或者把一个可调用的对象传给 Twisted 的函数,这些函数会产生事件。

6

其实我之前从没听过“好莱坞原则”这个说法,也不太了解Twisted(虽然我觉得我应该了解)。不过,控制反转这个概念并不难理解。我觉得用图形用户界面(GUI)编程来介绍它是个不错的选择。下面是一个简单的例子,来自这里(稍微修改过)。

import Tkinter

class App(object):
    def __init__(self, master):
        frame = Tkinter.Frame(master)
        frame.pack()

        self.button = Tkinter.Button(frame, text="QUIT", fg="red", command=frame.quit)
        self.button.pack(side=Tkinter.LEFT)

        self.hi_there = Tkinter.Button(frame, text="Hello", command=self.say_hi)
        self.hi_there.pack(side=Tkinter.LEFT)

    def say_hi(self):
        print "hi there, everyone!"

root = Tkinter.Tk()
app = App(root)

root.mainloop()

这是一个非常简单的控制反转的例子。它使用了回调,所以才有了“好莱坞原则”这个名字(谢谢Sven提供的链接)。这个概念是,你写了一个函数,但你并不直接调用它。相反,你把它交给另一个程序,并告诉那个程序什么时候调用它。然后你把控制权交给那个程序。下面是代码的详细解释:

import Tkinter

class App(object):

我们从一个类的定义开始,这个类会保存我们的回调函数,并把它们传递给我称之为“主程序”的适当部分。

    def __init__(self, master):

我们的类需要一个“主程序”;主程序就是会调用我们定义的函数的地方。在这个例子中,它是GUI的根窗口。更准确地说,在GUI编程的上下文中,我们可以说masterframe父级

        frame = Tkinter.Frame(master)
        frame.pack()

这两行代码创建了一个Frame对象,基本上就是一个装着小部件的盒子。稍后你会看到什么是小部件。正如你所看到的,它也有一个父级——和我们的App一样:master

        self.button = Tkinter.Button(frame, text="QUIT", command=frame.quit)
        self.button.pack(side=Tkinter.LEFT)

self.button是一个小部件。当你用Tkinter.Button创建它时,你给它一些属性,比如标签(text="QUIT")。你还告诉它它的父级是什么——在这个例子中,不是master,而是frame。所以现在我们有了一个层级关系——master -> frame -> button。但我们最重要的操作是:command=frame.quit。这告诉按钮在被鼠标点击时应该做什么。简而言之,这就是一个回调。在这里,我们把framequit方法传给它,这样做会让整个程序退出。注意这个函数后面没有()——这是因为我们不想直接调用它。我们只是想把它交给button

        self.hi_there = Tkinter.Button(frame, text="Hello", command=self.say_hi)
        self.hi_there.pack(side=Tkinter.LEFT)

这是另一个几乎和第一个相同的小部件,唯一的不同是我们传给回调的不是self.quit,而是self.say_hi。因为这个函数在下面定义,所以你可以在这里替换成任何你想要的函数。(在上面两组代码中,self.button.pack只是告诉Buttonframe中的位置。)

    def say_hi(self):
        print "hi there, everyone!"

say_hi就是你定义Hello按钮功能的地方。

root = Tkinter.Tk()
app = App(root)

现在我们调用我们的类,创建一个实例。我们创建根窗口,然后用root作为父级创建一个App实例。

root.mainloop()

然后我们就完成了。我们把控制权交给Tkinter,接下来就由它来完成剩下的工作。

撰写回答