想象你走进一家米其林三星餐厅。传统餐厅里,你翻着菜单,举手叫服务员:“给我来份牛排!”服务员跑向厨房;过一会儿你又举手:“再给我一份甜点!”——每一次需求都由你主动发起,就像写代码时主动调用库函数。而私人订制晚宴完全不同:你只需要坐下来,提交一张“口味档案”(比如“不吃香菜、爱海鲜、甜品偏好黑巧”),接下来整晚的节奏——何时上开胃菜、主菜如何衔接、甜点何时亮相——全都由主厨掌控。你不再追着厨师喊,而是主厨在最佳时机把一道道菜端到你面前:“这是您的时令前菜。”你被动等待,却享受了最流畅的体验。 这套“你只管描述偏好,流程由我掌控”的哲学,在软件工程里被称为好莱坞原则(Hollywood Principle)。它的口号只有一句话:“不要调用我们,我们会调用你。”组件不主动向框架伸手要资源、要时机,而是把“口味档案”(接口/回调)交给框架,由框架在你意想不到却恰到好处的那一刻发起召唤。看似被动的姿态,反而解开了依赖死结:组件与组件不必互相认识,彼此只认框架的“传菜口”。 本文会带你拆解这场晚宴的全部后厨秘密:控制反转如何把流程主权交给框架,依赖注入如何让主厨按档案备料,模板方法如何用固定骨架兜住千变万化的菜式,以及——当订制过度、菜单变成十页调查问卷时,这场宴会为什么反而难以下咽。

岗位关联

理解好莱坞原则是读懂 Spring、React 等一切主流框架的钥匙,也是面试中被反复追问的“控制反转/依赖注入”背后真正的哲学根基。掌握它,你会从“会用框架”进阶为“看懂框架”。

目录
  1. 可视化演示
  2. 原则核心
  3. 控制反转
  4. 依赖注入
  5. 模板方法
  6. 对比库调用
  7. 工程价值
  8. 实践警示
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

对比传统库调用、好莱坞原则(IoC)和依赖注入三种模式,展示控制权转移如何实现解耦与可测试性。

本节配有交互动效(进入视口后自动加载,约 3KB)。

传统库调用模式 App主动调用Library,控制权始终在调用方 Library被动响应,返回结果给调用方 App → Lib.call() → Result 调用方主导控制流,库被动响应

原则核心

晚宴开场前,主厨走到餐桌前微微一笑:“各位不必举手点菜,我根据你们的档案安排就好。”这一刻,宾客们从“发号施令的食客”变成了“被照顾的对象”。这并非地位下降,而是角色重新分工:宾客负责享受,主厨负责节奏。软件世界里,组件就是宾客,框架就是主厨。

这就是好莱坞原则的核心思想——1983 年,Richard Sweet 在好莱坞模板会议上提出这句口号:“不要调用我们,我们会调用你。”它描述了一种典型的协作关系:框架准备好场景和剧本,组件把实现细节交上去,由框架在恰当的时机调度组件。你写的类不是自告奋勇跑上台的演员,而是被导演按剧本叫到名字才出场的配角。

从调用关系上看,好莱坞原则让“呼叫方”和“被呼叫方”互换位置。传统编程里代码是主动方,框架是被动工具;好莱坞原则则让框架变成主动的调度者,代码变成被调度的对象。这种互换听起来简单,却彻底改变了程序的结构组织方式。

宾客们发现,这场晚宴自己全程没有举手,却每一道菜都恰好合口味。原因很简单:主厨拿了档案,而档案就是宾客交给主厨的“钩子”。原则核心讲完了,我们下一步看看主厨究竟怎样“抢走”了流程的指挥权——这就是控制反转。

好莱坞原则: Don’t call us⏟勿呼我们 We’ll call you⏟我们来呼你\displaystyle \text{好莱坞原则}:\ \underbrace{\text{Don't call us}}_{\text{勿呼我们}}\ \underbrace{\text{We'll call you}}_{\text{我们来呼你}}
这句话的落点是“我”——框架掌握打电话的主动权,你只需要把号码(回调/接口)留给框架。
  1. 前半句“勿呼我们”约束组件不要主动伸手向框架或兄弟组件索取,所有资源与时机由框架统一安排。
    Don’t call us\displaystyle \text{Don't call us}
  2. 后半句“我们来呼你”框架在流程推进到某一步时,通过接口或回调反向调用组件,组件被动响应。
    We’ll call you\displaystyle \text{We'll call you}
  3. 主语互换即反转两个半句的主语从“你”变成“我们”,正是控制权转移的文字写照,也是“反转”一词的由来。
    主动方:组件⟶框架变为框架⟶组件\displaystyle \text{主动方}: \text{组件} \longrightarrow \text{框架} \quad \text{变为} \quad \text{框架} \longrightarrow \text{组件}

控制反转

主厨接管晚宴后,第一件事就是宣布固定的上菜顺序:开胃菜、汤品、主菜、甜点。宾客不需要知道后厨什么时候备料、什么时候摆盘,只需要在对应环节出现时品尝。流程的指挥棒从宾客手里转移到了主厨手里,这就是“控制反转”(Inversion of Control,IoC)。

技术视角看,控制反转是指程序执行流程的主权从开发者代码转移到框架容器。传统模式下,开发者写 main 函数一步一步驱动一切;而在 IoC 模式中,框架的 main 逻辑由框架自己持有,开发者的代码变成被框架调用的“演员”。框架决定调用顺序、调用时机,插件(开发者的代码)只负责在被召唤时演好自己的段落。

控制反转有两条清晰的“命令方向”:组件实现接口来声明自己“会做什么”,框架在合适时机调用接口。这样就形成了分工——框架管“何时做”,组件管“怎么做”。模板方法模式和依赖注入都是这一思想的具体落地,前者管流程骨架,后者管资源配送。

宾客们发现,虽然自己“被动”了,但再也不用纠结“下一道菜是什么、该催谁”——主厨全都包办。流程主权交出去之后,另一个问题也随之而来:主厨怎么知道每道菜用什么食材?答案就是依赖注入——按档案备料。

传统:组件⏟调用者→框架⏟被调者IoC:框架⏟调用者→组件⏟被调者\displaystyle \text{传统}: \underbrace{\text{组件}}_{\text{调用者}} \rightarrow \underbrace{\text{框架}}_{\text{被调者}} \qquad \text{IoC}: \underbrace{\text{框架}}_{\text{调用者}} \rightarrow \underbrace{\text{组件}}_{\text{被调者}}
两条链的箭头方向相反,这是“反转”最直观的数学表达:调用的指挥棒换了主人。
  1. 传统调用方向组件主动调用框架提供的工具函数,类似宾客跑进后厨自己端菜。
    组件→框架\displaystyle \text{组件} \rightarrow \text{框架}
  2. 反转后的调用方向框架按流程调用组件实现的方法,类似主厨按节奏上菜到宾客面前。
    框架→组件\displaystyle \text{框架} \rightarrow \text{组件}
  3. 接口是中间契约组件以“实现接口”的方式向框架登记自己的能力,框架持接口引用完成反向调用。
    组件→实现接口框架→持有并调用接口\displaystyle \text{组件} \xrightarrow{\text{实现}} \text{接口} \quad \text{框架} \xrightarrow{\text{持有并调用}} \text{接口}

依赖注入

主厨手里有一叠宾客档案,上面写着“不吃香菜、爱海鲜、甜品偏好黑巧克力”。备料时,主厨不征求宾客意见,直接按档案把对应食材送到宾客面前——这就是依赖注入(Dependency Injection,DI):资源不请自来,而不是由组件自己去后厨寻找。

依赖注入是控制反转最典型的实现方式,解决的核心问题是“组件所需的依赖从哪来”。没有 DI 时,组件会自己 new 一个依赖对象,就像宾客自己冲进厨房抢食材;有了 DI 后,容器(container)成为中间人,负责创建所有依赖实例,并在组件构建时把依赖“端”进去。组件完全不关心依赖是谁创建、何时创建。

注入有三种常见方式:构造函数注入(食材在开宴时就位,依赖不可缺)、Setter 注入(上菜途中可更换配菜,依赖可缺省)、接口注入(通过接口方法传入依赖)。三者本质相同——都是“容器送到,组件不取”。你只需要在配置里声明“我要什么”,容器就会像主厨一样把对应的依赖对象递过来。

宾客从未离开座位,可桌上的菜总是换着花样端来。档案怎么填,菜就怎么上:依赖注入让组件之间彻底不碰面,只与容器打交道。顺着这个思路再看一层,主厨上菜的“固定顺序”又该怎么设计?这就轮到模板方法登场了。

依赖注入:容器⏟中间人⟶组件⏟消费方: 所需依赖\displaystyle \text{依赖注入}:\quad \underbrace{\text{容器}}_{\text{中间人}} \longrightarrow \underbrace{\text{组件}}_{\text{消费方}}: \ \text{所需依赖}
箭头从容器指向组件,表明依赖不是组件主动伸手去拿,而是容器主动送上门。
  1. 容器(中间人)容器集中管理所有对象的创建与生命周期,是最懂“后厨备料”的角色。
    容器=依赖的创建与持有者\displaystyle \text{容器} = \text{依赖的创建与持有者}
  2. 组件(消费方)组件只声明“我需要什么类型的依赖”,从不关心依赖来自何方。
    组件: 构造参数列表=所需依赖\displaystyle \text{组件}: \ \text{构造参数列表} = \text{所需依赖}
  3. 注入动作容器在实例化组件时把依赖传入构造参数或 Setter,完成“端菜上门”。
    容器⟶组件注入依赖\displaystyle \text{容器} \longrightarrow \text{组件}^{\text{注入依赖}}

模板方法

晚宴的流程表面上轻松,背后却有一条雷打不动的铁律:开胃菜永远先于主菜,甜点永远最后登场。主厨不会因为宾客喜欢甜点就提前上它——因为整套宴席的骨架是固定的。每个环节内做什么可以变,环节的顺序不能乱,这就是模板方法模式。

模板方法模式定义了一个算法的骨架,把某些步骤延迟到子类实现。父类(框架/主厨)负责把步骤按固定顺序串联起来,子类(插件/宾客的口味档案)只负责“填空”:开胃菜是烟熏三文鱼还是蔬菜塔,主菜是牛排还是龙虾——这些由具体的子类决定。父类中留出的空位称为钩子方法(hook),就好比菜单上的“今日例汤”:框架不知道今天喝什么汤,但它知道“喝汤”这一步一定在开胃菜之后、主菜之前。

钩子方法让“骨架固定”与“细节多变”完美共存。你可以新增一个子类,把一个全新的“口味”填进钩子里,而完全不用改动父类的流程代码。一个框架因此能服务无数种业务场景——主厨只要守住上菜顺序,就能应对任何宾客的偏好。

主厨守住了上菜顺序,宾客换了一拨又一拨,宴席永远有条不紊。这种“骨架在框架、血肉在插件”的分工,恰恰是好莱坞原则的教科书示范。不过,要真正理解它为什么叫“反转”,还得看清它和传统库调用的本质差异——点餐与订制的区别。

templateMethod()=step1→step2→⋯→hook()→⋯→stepn⏟固定骨架\displaystyle \text{templateMethod}() = \underbrace{\text{step}_1 \to \text{step}_2 \to \cdots \to \text{hook}() \to \cdots \to \text{step}_n}_{\text{固定骨架}}
骨架里的步骤顺序是固定的,钩子位置就是流程中特意留下的“可替换空位”,由子类来填空。
  1. 步骤序列整体流程被拆成若干步骤,由父类按固定次序串联执行。
    step1→step2→⋯→stepn\displaystyle \text{step}_1 \to \text{step}_2 \to \cdots \to \text{step}_n
  2. 钩子方法序列中预留的抽象或空方法,默认实现为空,等待子类覆盖。
    hook()={子类实现若覆盖默认空操作否则\displaystyle \text{hook}() = \begin{cases} \text{子类实现} & \text{若覆盖} \\ \text{默认空操作} & \text{否则} \end{cases}
  3. 子类填空子类只需要实现钩子,即可在不动骨架的前提下改变局部行为。

对比库调用

旧式餐厅里,你翻开菜单,指着一道菜说“我要这个”,服务员记下后替你下单——这是库调用:你的代码主动调用库函数,库被动执行完后返回结果,控制权始终在你手里。库就像一件趁手的厨具,你决定什么时候用它、怎么用它;它永远不会说“现在该切洋葱了,你来配合我”。

而框架调用完全不同:框架自己写好了完整的“剧本”,它规定什么时候执行哪一步,你的代码只是剧本里的一个角色。你实现一个方法,框架会在某个时机自动调用它,就像主厨在八点整准时上主菜——不需要你提醒,甚至不允许你抢拍。库与框架的本质区别可以浓缩为一句话:“你调用库,框架调用你。”

这种差异带来的选择标准也很清晰:如果你的业务流程由自己完全掌控、只想要现成的工具,选库更轻便;如果你希望把流程骨架外包出去、专注于填充业务细节,选框架更合适。Spring、React、Django 都是典型的框架,而 requests、Lodash 则是标准的库。

宾客们心里有了数:想随意点菜就去库模式的餐厅,想享受完整晚宴体验就去框架主导的订制餐厅。理解了这个区别,也就不难理解好莱坞原则给工程带来的真正价值——为什么那么多团队愿意把“主动权”交出去?因为交出去之后,收益超乎想象。

库模式:程序⏟主动→库功能⏟被动框架模式:框架⏟主动→插件⏟被动\displaystyle \text{库模式}: \underbrace{\text{程序}}_{\text{主动}} \to \underbrace{\text{库功能}}_{\text{被动}} \qquad \text{框架模式}: \underbrace{\text{框架}}_{\text{主动}} \to \underbrace{\text{插件}}_{\text{被动}}
两条表达式的差别就是“主动者是谁”——库的主动者是程序,框架的主动者是框架自身。
  1. 库模式箭头程序作为调用者调用库函数,库执行并返回,调用链是程序发起。
    程序→库\displaystyle \text{程序} \to \text{库}
  2. 框架模式箭头框架在流程推进中调用插件实现的方法,调用链是框架发起。
    框架→插件\displaystyle \text{框架} \to \text{插件}
  3. 被动方语义两式中的“被动”方都只提供实现、不决定时机,恰似主厨定节奏、宾客不催菜。

工程价值

主厨接手晚宴后,宾客们享受到了三件好事:想换菜时只需修改档案而不用重排整个流程;想提前试菜时主厨先用“仿真食材”演练一遍;临时加一道菜也不影响其他环节。映射到软件工程,这正是好莱坞原则的四大工程价值:降低耦合、测试友好、扩展灵活、关注分离。

降低耦合:组件不再互相 new 对方,所有依赖都通过接口+容器中转,修改一个组件不会牵连其他组件。测试友好:单元测试时,你可以把真实依赖替换成 Mock(仿真食材),不需要启动数据库或网络服务,就能独立验证组件逻辑。扩展灵活:新增一个实现类就像新填一张口味档案,只要符合接口约定,框架无需改动就能接入新功能。关注分离:业务代码只关心“做什么”,框架只关心“何时做”,各司其职。

这四大价值可以凝练成一个关系式:系统的可测试性与“可注入程度”成正比。依赖越容易被替换,测试越轻松;硬编码的依赖越多,测试越沉重。这也解释了为什么依赖注入容器在现代后端项目中几乎是标配——它不是炫技,而是让代码“可被检验”的工程底线。

主厨的订制服务让晚宴轻松且多变,宾客们不禁感叹:把主动权交给专业的人,原来是这么划算的事情。不过,凡事过犹不及——如果宾客的档案填得过于细致,每道菜都要确认食材产地、温度、摆盘,晚宴反而会变得疲惫不堪。好莱坞原则的实践,同样有一道必须警惕的坎。

易测性∝可注入的 Mock 依赖硬编码的具体依赖\displaystyle \text{易测性} \propto \frac{\text{可注入的 Mock 依赖}}{\text{硬编码的具体依赖}}
分子越大、分母越小,系统越容易测试——这就是好莱坞原则把控制权交给框架后换来的工程红利。
  1. 分子(可注入 Mock 依赖)依赖通过外部注入,测试时就能换成 Mock 对象,分子越大意味着越容易构造测试场景。
    可注入 Mock 依赖\displaystyle \text{可注入 Mock 依赖}
  2. 分母(硬编码的依赖)代码内部直接 new 出来的依赖无法替换,分母越大测试越困难。
    硬编码的具体依赖\displaystyle \text{硬编码的具体依赖}
  3. 正比关系整个比值表示“易测性”随可替换空间增大而提升。

实践警示

想象一位过度认真的宾客,在口味档案里写了五十条备注:“香菜必须来自云南”“牛排要三分熟但边缘焦脆”“甜点不能含乳糖但要有奶香”。主厨每做一道菜都要翻三遍档案、打五个电话确认——晚宴从享受变成了折磨。这就是过度配置化的缩影:控制反转赋予了框架强大能力,但滥用这种能力会制造出比传统代码更复杂的迷宫。

过度依赖框架和容器会带来三个典型问题。其一是框架魔法:调用链被层层封装,代码里看不到实际的流程,出了问题像在追查“隐形主厨”。其二是隐式依赖:组件之间的依赖关系藏在配置文件里而非代码中,IDE 跳转不到、静态检查查不出,新人接手根本不知道“谁在调用谁”。其三是调试困难:堆栈里全是框架内部调用,业务代码被埋得很深,定位一个问题要在多层框架间来回跳跃。

因此,实践好莱坞原则必须讲究平衡之道:能用普通函数解决的问题,不必强行引入框架;能写清楚的显式调用,不必全部塞进容器;配置超过必要限度时,果断回归简单。记住一个简单的复杂度模型——每多一层框架,复杂度就叠加一层配置成本。配置不是越少越好,但更不是越多越好,够用就好。

晚宴结束后,主厨感慨:最好的订制是“刚好记得你不吃香菜,恰好知道你爱黑巧”。不多不少,恰到好处。好莱坞原则也是如此——把控制权交给框架,但别把思考权也一并交出去。理解原理、把握尺度,这场“主厨召唤宾客”的程序宴会,才能真正做得美味又优雅。

总复杂度=C业务+n×C配置(n=框架层数)\displaystyle \text{总复杂度} = C_{\text{业务}} + n \times C_{\text{配置}} \quad (n = \text{框架层数})
业务复杂度不可回避,而配置复杂度会随框架层数线性放大——层数越多,系统越臃肿难查。
  1. 业务复杂度业务本身的复杂度无法消除,是复杂度的基础项。
    C业务\displaystyle C_{\text{业务}}
  2. 配置复杂度每引入一层框架,就多出一份配置成本与隐藏逻辑。
    C配置\displaystyle C_{\text{配置}}
  3. 层数加权框架层数 n 越多,总复杂度被放大得越严重,这就是过度配置的数学代价。

AI 应用场景

Spring 框架中的依赖注入容器

阶段:编码与架构设计

解决的问题:传统业务代码中 Service 直接 new 一个 Dao 对象,导致模块间强耦合、单元测试难以替换数据库。借助依赖注入容器,所有 Bean 的创建与装配交由容器统一管理,Service 只声明接口依赖,容器在启动时自动注入,模块间彻底解耦。这就像主厨按档案备料——各组件只报“我要什么菜”,后厨自动送来。

具体案例:在用户管理系统中,UserService 的构造函数声明需要一个 UserRepository 接口,Spring 容器启动时自动将 JdbcUserRepository 的实例注入进去。测试时只需替换为内存版 MockUserRepository,无需启动数据库即可跑完测试,整个替换过程零改动业务代码。

前端框架的事件回调机制

阶段:交互设计实现

解决的问题:浏览器页面需要响应用户点击、输入等事件,若用传统方式,代码必须轮询状态或管理大量监听器,逻辑混乱。React、Vue 等框架实施好莱坞原则——框架监听事件,在适当时机调用开发者注册的回调函数,开发者只需声明“发生什么之后做什么”。

具体案例:开发一个搜索框,开发者只需写好 useEffect 回调并在其中声明“当搜索词变化时请求接口”,React 会替开发者监视状态变化并自动触发该回调。开发者不主动调用渲染流程,渲染流程由框架随时召唤——像主厨叫到你的号,你只管端出拿手菜。

单元测试中的 Mock 替换

阶段:测试与质量保障

解决的问题:测试依赖外部服务(数据库、支付接口、第三方 API)时,环境不可控导致测试不稳定。好莱坞原则的思路是让被测组件不主动获取依赖,而是由测试框架注入替身——这与依赖注入的思路完全同源,只是“主厨”换成了测试代码。

具体案例:测试订单服务时,用 unittest.mock 将支付网关替换为预置返回值的假实现,注入订单服务后,业务逻辑可以在无网络环境下反复验证。测试用例不再依赖真实环境,就像主厨先用手工仿真食材试菜,确认味道后再进真实厨房。

Python 演示

python52 行
"""主厨框架:好莱坞原则的 Python 演示(模板方法 + 极简依赖注入)"""  class Banquet:    """晚宴框架:固定宴席骨架,控制反转由框架掌握"""     def __init__(self, dessert_provider=None):        self.dessert_provider = dessert_provider     def run(self):        print("[框架] 宴会开始,按固定流程执行")        self.step_appetizer()        self.step_main_course()        self.hook_dessert()        print("[框架] 宴会结束")     def step_appetizer(self):        print("[框架] 上开胃菜")     def step_main_course(self):        print("[框架] 上主菜")     def hook_dessert(self):        """钩子:默认无甜点,子类或注入覆盖"""        if self.dessert_provider:            self.dessert_provider()        else:            print("[框架] 默认不上甜点")  class ChefPlugin(Banquet):    """插件:在预留钩子处填充细节"""     def step_main_course(self):        print("[插件] 招牌牛排上桌")  def di_dessert():    """依赖注入:把甜点这个依赖端给框架"""    def serve():        print("[注入] 黑森林蛋糕登场")    return serve  def main():    dessert = di_dessert()    feast = ChefPlugin(dessert_provider=dessert)    feast.run()  if __name__ == "__main__":    main()
目的:用一场“晚宴”演示好莱坞原则——框架按固定骨架驱动流程,插件填充钩子,依赖通过注入方式送达。
  1. class Banquet:框架类,掌握整个流程调度权,对应好莱坞原则中的“主厨”。
  2. def run(self):模板方法,按固定顺序串联步骤,子类无法改变流程顺序。
  3. def hook_dessert(self):钩子方法,默认不做事,留给子类或外部注入填补。
  4. self.dessert_provider()框架在钩子位置主动调用注入的依赖,实现“框架调用你”。
  5. class ChefPlugin(Banquet):插件类,在模板方法的主菜步骤处填充细节。
  6. def di_dessert():依赖注入的简化模拟:把“甜点”这个依赖对象作为参数传给框架。
  7. ChefPlugin(dessert_provider=dessert)通过构造函数注入依赖,容器不再现身但注入思想完整。
  8. feast.run()整个流程由框架驱动,插件只等被召唤。

常见误区

误区“好莱坞原则不就是 Spring 的依赖注入容器吗?我把 XML 里配几个 bean 就完事了。”

正解依赖注入只是好莱坞原则的一种实现方式,并不是原则本身。好莱坞原则更广义,凡是“框架主动调用你的代码”都属于它,模板方法、回调函数、事件监听都是它的表现。依赖注入解决“资源怎么送到”,模板方法解决“流程怎么串起来”,两者都是同一哲学的不同侧面。

为何常见:Spring 太流行,很多初学者接触控制反转的第一站就是依赖注入容器,于是把局部当整体。

误区“库和框架不都是别人写好的代码给我用吗?叫法不同而已。”

正解两者的调用方向完全相反。库是你主动调用它,你控制流程;框架是它主动调用你的代码,框架控制流程。打个比方:库是菜单上的菜,你点它才上桌;框架是主厨的晚宴,他定好节奏一道道端给你。方向不同,架构姿态就完全不同。

为何常见:从使用感受看,两者都是“引入外部代码”,很难第一眼看出控制权差异。

误区“把流程交给框架,那我的代码不就成了被摆布的傀儡?想自定义个逻辑都难了。”

正解控制反转失去的是“流程编排权”,不是“业务实现权”。你失去的是决定“何时调用”的权利,但你握紧的是“做什么、怎么做”的权利——通过实现接口、注入依赖,你在更合适的位置表达了自己的需求。框架是替你守住了流程纪律,而不是替你决定业务。

为何常见:把“控制”理解成“自由发挥”造成的误解,其实这里控制专指流程执行权。

误区“子类重写父类方法而已,这不就是面向对象的多态吗?模板方法没什么新意。”

正解模板方法的重点不在“重写”而在流程复用。它先把整个算法骨架固化在父类,只留下特定步骤给子类填充,保证流程的完整顺序不被破坏。普通多态覆盖随时可能打乱流程,而模板方法像晚宴的固定上菜顺序——你只能换菜色,不能换顺序。

为何常见:看到“父类定义、子类覆盖”就直觉想到多态,忽略了骨架在模板方法中的约束地位。

误区“反正是解耦神器,把项目里所有对象都交给容器管理,配置得越多越显专业。”

正解过度配置化会把系统变成“框架魔法迷宫”——依赖关系藏在配置里,代码中看不到调用链,调试时堆栈全是框架内部逻辑,业务问题反而难定位。每多一层框架就多一份配置成本,复杂度会随框架层数线性增长。平衡之道是:简单场景用显式调用,复杂场景才引入容器。

为何常见:把工程价值放大成了绝对优势,忽略了过度抽象的代价,这是初学者常见的心态。

面试题

Q1. 好莱坞原则的核心口号是?

单选题 · easy

  • A.先来先服务
  • B.不要调用我们,我们会调用你← 正确答案
  • C.一切皆对象
  • D.约定优于配置

答案:B。B 正是好莱坞原则的原话。A 是排队策略,C 是面向对象的口号,D 是另一套框架设计理念。核心在于“框架主动调用你”。

Q2. 下面哪个场景最能体现控制反转?

单选题 · easy

  • A.程序循环遍历数组求和
  • B.浏览器触发 onclick 回调← 正确答案
  • C.函数内直接 new 一个对象
  • D.使用 Collections 工具类排序

答案:B。浏览器(框架)在点击事件发生时调用你注册的回调函数,控制权在框架侧。其余选项都是程序主动调用工具或直接创建对象。

Q3. 哪些属于依赖注入的常见注入方式?

多选题 · mid

  • A.构造函数注入← 正确答案
  • B.Setter 方法注入← 正确答案
  • C.接口注入← 正确答案
  • D.全局变量注入

答案:ABC。构造、Setter、接口是经典三种注入方式。全局变量不是注入,反而引入隐式耦合,违背解耦初衷。

Q4. 库调用与框架调用的本质区别是?

单选题 · mid

  • A.代码执行速度不同
  • B.控制权归属不同← 正确答案
  • C.编程语言不同
  • D.文件体积不同

答案:B。库由你的代码主动调用,框架反向调用你的代码,核心差异是“谁握着控制权”。速度、语言、体积都不是本质属性。

Q5. 模板方法模式中钩子方法的特征有?

多选题 · mid

  • A.由父类定义默认实现← 正确答案
  • B.子类可覆盖改变细节← 正确答案
  • C.框架在固定时机调用它← 正确答案
  • D.必须由外部线程调用

答案:ABC。钩子由父类提供默认空实现,子类覆盖,框架在骨架固定时机调用。它不需要外部线程,D 是干扰项。

Q6. 在依赖注入中,容器的主要职责是?

单选题 · easy

  • A.存储业务数据
  • B.创建并装配依赖← 正确答案
  • C.编译源码
  • D.打印日志

答案:B。容器负责创建实例、管理生命周期、按声明装配依赖关系,相当于主厨的后厨——所有食材由它统一调度。

Q7. 好莱坞原则带来的工程价值包括?

多选题 · mid

  • A.组件间降低耦合← 正确答案
  • B.单元测试更易注入替身← 正确答案
  • C.功能扩展更灵活← 正确答案
  • D.代码行数必然减少

答案:ABC。解耦、易测试、可扩展都是核心价值。代码行数不一定减少,配置反而可能增加,D 是常见误解。

Q8. 过度配置化最主要的危害是?

单选题 · hard

  • A.程序启动速度变快
  • B.隐式依赖让调试与维护困难← 正确答案
  • C.类数量自动减少
  • D.内存占用必然为零

答案:B。过多配置把依赖关系藏进文件与框架魔法中,代码里看不到调用链,出错时极难追踪。A、C、D 与事实相反。

Q9. 构造函数注入相比 Setter 注入的主要优势是?

单选题 · mid

  • A.代码行数更短
  • B.依赖在创建期完整且不可变← 正确答案
  • C.不需要定义接口
  • D.天然支持循环依赖

答案:B。构造注入要求对象创建时提供全部依赖,保证对象一旦生成就处于完整可用状态,且依赖不可被中途篡改。循环依赖恰恰是构造注入更难处理的情况。

Q10. 让组件可测试性提升的机制包括?

多选题 · hard

  • A.依赖可通过构造函数替换为 Mock← 正确答案
  • B.框架负责装配使组件不主动 new 依赖← 正确答案
  • C.组件只依赖接口而非具体类← 正确答案
  • D.组件内部使用全局单例

答案:ABC。可注入、被动装配、面向接口都是提升可替换性的关键。全局单例在测试时极难替换,反而降低可测试性,D 是反向做法。