《编程导论(Java)》在【9.3.1回调】中,介绍了好莱坞法则/好莱坞原则(Hollywood principle)第287页,并将它作为回调的近义词,即当程序中使用了回调,那么你的程序应用了"好莱坞法则"。
- Hollywood principle:"Don't call me; I'll call you." (don't call us, we'll call you)
对于好莱坞原则的解释,在各种书籍、文献中极其混乱。本文介绍《编程导论(Java)》中对好莱坞原则的解释,以及为什么要这样解释。不可避免地,本文会把一些著名文献中的解释作为反面例子。
(重写 别了,DIP、IoC)
不懂递归,不要说学过算法;
不懂回调,不要说学过编程;
1.引子:多态
图1 依赖于抽象类型、针对接口编程、遵循OCP 代码的基本结构
Client有方法test,代码为:
- IServer s = new Server();
- s.foo(5);
对上述代码的解释:“s被初始化后,将按照s指向对象的实际类型(Server), 动态绑定 foo()的代码块。”
这是面向对象中基本的解释方式,后面的讨论将涉及到这一解释。
这里,yqj2065设定了一个前提:我不喜欢对上述代码的其他云山雾绕的解释!
2.回调
在分层结构中,上层依赖于下层,最后依赖于基础设施(如JDK、各种框架)。因而
依赖必须是单向的。
如果图1中的Server是一个上层模块,那么它给出的
foo(int)代码块称为回调。注意:按照
依赖的单向性,因为子类依赖父类,如果两者不同层,子类必须为上层模块。
回调与通常的非回调代码,从类图和其自身代码上,没有区别——都是调用下层模块的一个接口。之所以需要
回调callback、隐式调用
Implicit invocation(某些软件架构的作者使用的术语),是因为分层结构的一条线的存在,如图2所示。
图2 回调的应用场景
图3 回调的基本结构
- 回调与通常的非回调代码,从类图和其自身代码上,没有区别。
- (为了使用回调)回调机制不得不在下层(或基础设施/框架中)定义一个Client的父类型IClient/IXxx,它定义了回调callback(int)的方法头(方法的接口)
- (Java中)回调与通常的(遵循OCP 代码的)非回调代码,使用的技术不过是动态绑定/多态。
3.好莱坞法则
"Don't call me; I'll call you." 注意:本书中,好莱坞原则中的
me是下层模块!
通常,Client即you调用下层Server即me天经地义,但是,对于某些方法,
请
你不要轮询/骚扰我,我通知你。
现实生活中乘客/you打的士到某地,沿途问司机/me某个景点天经地义;但是不要从上车的第一秒开始,时刻或每隔5秒问司机到了打的的目的地没有,这也太烦人了。
- 好莱坞原则、回调不改变分层结构的依赖方向——
控制反转Inversion Of Control/IoC这个乱七八糟术语没有存在的价值(充其量说明了软件的用户体验)。 - 如果不采用通知方式——即不应用好莱坞原则,上层可以轮询。(请自己编写一个演示程序)
好莱坞原则的核心:以通知替代轮询。
4.类层次与好莱坞原则
一些著名文献中的反面例子。
反面例子之一:
http://www.as3dp.com/category/design-patterns/template-method-pattern/
“Be sure to go over the more detailed post on the Hollywood Principle, but for here, you just need to become familiar with the idea that
higher level components (like parent classes and interfaces) should call lower level components (like concrete classes), but lower level components, should not call higher level components. The principle is called Hollywood, because the casting director tells the budding actor,
Don’t call us, we’ll call you.
The parent class tells the child class the same thing.”
Don’t call us, we’ll call you.
The parent class tells the child class the same thing.”
驳斥:
- 从依赖关系上看,按照分层和依赖的单向性,子类是上层模块、父类是下层模块(如果不在同一层的话)。因为子类可以(必须的)依赖父类型,你不可能将父类型放在上层,而让下层(的子类)依赖于上层。所以上文中的下划线部分本身就是错误的。
- 如“1.引子:多态”所言,有动态绑定,在类层次中使用回调、好莱坞原则的解释是一种极其无聊的解释,而且令人困惑。
- 按照他的说法,好莱坞原则“应该”意味着“上层调用下层而下层不得调用上层”;而这一“上层调用下层而下层不得调用上层”,我们称之为单向依赖,不需要搞一个多此一举的“好莱坞原则”作为单向依赖的同义词。当然,如果上层模块说"Don't call me; I'll call you.",非常符合该字面的解读,这种上层模块所说的好莱坞原则,和白开水一样,没有营养。
反面例子之二:[设计模式]
“模板方法导致一种
反向的控制结构,这种结构有时被称为
“好莱坞法则” ,即“别找我们,我们找你”[ S w e 8 5 ]。这指的是
一个父类调用一个子类的操作,而不是相反。”
驳斥:
- 不管模板方法的细节,如“1.引子:多态”所言,s.foo(5)动态绑定,完全不需要”父类调用子类的操作“的说法。父类不可能知道子类的附加的操作,那么s.foo(5)是Client调用IServer的foo然后IServer调用子类Server的overridr方法foo?
- [ S w e 8 5 ]的原文:Don't call us, we'll call you (Hollywood's Law): A tool should arrange for Tajo tonotify it when the user wishes to communicate some event to the tool, rather than adopt an 'ask the user for a command and execute it' model.",和本文的【3.好莱坞法则】一致,而非【设计模式】的滥用。
- 按照面向对象的一般概念,子类是一个父类,父类动物有eat()方法,那么我们喂狗吃的时候即new Dog().eat(),有没有人说:”我喂动物吃,而动物调用狗的吃方法“,”我又喂动物吃,而动物调用猫的eat()方法“?太别扭了。
反面例子之三:[敏捷软件开发]11.2.1接口所有权倒置:
Don’t call us, we’ll call you。
低层模块实现了在高层模块中声明并被高层模块调用的接口。
驳斥:
- 在分层结构中,上层依赖于下层,依赖必须是单向的。他的书中的图11.2除了看起来有点道理外,层次真的能够”倒置“吗?假设图11.2中的Utility是JDK或某个框架,你倒置给我看看。
- 他的书中的图11.2中的三个包,如果忽略policy、mechanism和Utility的寓意,从上到下事实上是Utility-mechanism-policy的普通结构,从结构上没有任何特别的地方;如果强调policy、mechanism和Utility的寓意,而且按照依赖的单向性,父类必须是下层模块,因而图11.2并无意义,实践中接口所有权倒置,其意义下图所示。通常将所谓的”policy server interface“等放入公共模块(而非好看地与Policy Layer放在一个包中)。总之,依赖关系没有变化,DIP的“倒置”没有什么营养。从软件开发管理的角度,DIP是有启发意义的,但是DIP既不是架构设计的原则,也不是面向对象类关系的设计原则。
- 低层模块不得”实现高层模块中声明并被高层模块调用的接口“。正如反面例子之一的第一条反驳,你不可能将父类型放在上层,而让下层(的子类)依赖于上层。
- 接口所有权倒置,其意义如图3:
传统3层结构接口由BLL定义(所有权上移),但是BLL作为Mapper的下层模块。
反面例子之四:
http://c2.com/cgi/wiki?HollywoodPrinciple
”Hollywood Principle is often called InversionOfControl,
or Dependency Injection.“
说明:
1.回调机制是框架设计的基本手段。即使勉强接受IoC是好莱坞原则的同义词,但是依赖注入、事件驱动编程/GUI等等,都是回调机制、好莱坞原则的应用。
2.用于事件驱动编程/GUI中的好莱坞原则,与依赖注入一毛钱的关系都没有。
如果站在下层模块的角度,即me是下层模块,所说的好莱坞法则是对的,它是框架设计的基本原则。相关术语有:回调、GUI编程/事件驱动编程、观察者模式、依赖注入、分布式编程(RMI中也经常使用回调机制)、隐式调用、(控制反转)……Java的匿名类,C++的函数指针、C#的λ表达式……