能否提供一个简单的Python示例来解释“好莱坞原则”/控制反转?
我对Twisted这个网络框架非常感兴趣。根据我所了解,这个框架使用了“好莱坞原则”。我只知道这个词,但对这个设计模式完全不懂。 我在网上搜索了很多关于好莱坞原则在Python中实现的内容,但结果很少。 有没有人能给我一些简单的Python代码,来说明这个设计模式呢?
3 个回答
“好莱坞原则”这个名字来源于好莱坞电影行业。在这个行业里,紧张的第一次试镜演员常常会在试镜后不停地打电话给制片公司,询问自己是否获得了角色。在软件开发中,我们把这种行为称为轮询,这种做法非常低效。在好莱坞的比喻中,这不仅浪费了成功申请者的时间(因为他们可能在选角之前就反复打电话),也浪费了制片公司的时间(因为很多未被选中的申请者仍然会打电话询问)。
下面是一个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()
这当然是非常浪费的,因为谁知道演员们可能会在制片公司做出选择之前打多少次电话呢?
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 的函数,这些函数会产生事件。
其实我之前从没听过“好莱坞原则”这个说法,也不太了解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编程的上下文中,我们可以说master
是frame
的父级。
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
。这告诉按钮在被鼠标点击时应该做什么。简而言之,这就是一个回调。在这里,我们把frame
的quit
方法传给它,这样做会让整个程序退出。注意这个函数后面没有()
——这是因为我们不想直接调用它。我们只是想把它交给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
只是告诉Button
在frame
中的位置。)
def say_hi(self):
print "hi there, everyone!"
say_hi
就是你定义Hello
按钮功能的地方。
root = Tkinter.Tk()
app = App(root)
现在我们调用我们的类,创建一个实例。我们创建根窗口,然后用root
作为父级创建一个App
实例。
root.mainloop()
然后我们就完成了。我们把控制权交给Tkinter,接下来就由它来完成剩下的工作。