大家好我是程序员小策。你有没有遇到这种情况凌晨 2 点告警炸了。支付的 QPS 从正常的 5000 突然掉到 200用户端全部超时。你打开监控一看——CPU 没满内存没满但数据库连接池满了全是Connection timed out。翻日志翻到凌晨 3 点才定位到根因一个新加的通知功能。产品说支付成功后发个短信于是开发小哥直接在支付接口里new SmsClient()发短信每条短信耗时 80ms。5000 QPS × 80ms 400 秒的阻塞——连接池瞬间被掏空。就一行new把一个支付系统搞崩了。这不是编的故事——这就是没有依赖注入 门面模式的代码在生产环境上最经典的翻车现场。问题定义一坨代码长什么样先看看出事的代码长什么样——也是大部分新人包括当年的我觉得没啥问题的写法# 反例一个方法里干了 5 件事classPaymentController:defpay_exam_fee(self,student_id:str,amount:float):# 1. 连数据库dbMySQLConnection(localhost,root,password)# 2. 写费用记录db.execute(INSERT INTO fees (...) VALUES (...))# 3. 调定价引擎pricingPricingEngine()final_pricepricing.calculate(student_id,amount)# 4. 发短信通知smsSmsClient(api-key-xxx)sms.send(student_id,f扣费成功{final_price})# 5. 发邮件通知emailEmailClient(smtp.xxx.com)email.send(student_id,f扣费通知)Java 开发者的版本你也一定见过// 反例Spring Boot 里的 伪三层架构RestControllerpublicclassPaymentController{PostMapping(/pay)publicResultpay(RequestBodyPayRequestreq){// 直接在 Controller 里 new 了一堆依赖JdbcTemplatejdbcnewJdbcTemplate(dataSource);PricingServicepricingnewPricingService();SmsClientsmsnewSmsClient(api-key);EmailClientemailnewEmailClient(smtp-host);jdbc.update(INSERT INTO fees ...);BigDecimalpricepricing.calculate(req.getStudentId(),req.getAmount());sms.send(req.getStudentId(),扣费成功);email.send(req.getStudentId(),扣费通知);returnResult.ok();}}这代码有两个致命问题问题一调用方自己造依赖。谁用谁new结果就是到处散落着new SmsClient()。哪天想切个短信供应商改 200 处代码。问题二调用方自己编排流程。Controller 知道先写库、再定价、再发短信、再发邮件的完整流程。哪天多了一个写审计日志的步骤又是改 Controller。这就是我们今天要解决的问题。核心概念一个好公司和一个烂公司依赖注入Dependency Injection不要自己找资源声明你需要什么由外部递给你。门面模式Facade Pattern把多个子系统的复杂交互封装成一个简单的入口调用方只跟门面打交道。用职场类比一分钟讲清楚这两个模式。一个好公司有 DI Facade你刚入职需要领一台电脑。烂公司做法HR 跟你说自己去 IT 部找张三张三四点在 3 楼 B 区不在的话找李四李四在 2 楼 A 区……你自己搞定好公司做法入职流程Facade直接帮你搞定一切。HRDI 容器给你安排好电脑、工位、门禁卡——你只需要入职那一天出现在公司门口。翻译回技术语言你Controller/Service声明你需要什么def __init__(self, repo: UserRepository)而不是自己去newHR 系统DI 容器/IOC 容器负责把依赖注入给你入职流程Facade隐藏了 IT 部、行政部、安保部的内部协调你只需要调一个入职()方法DI 和 Facade 各自解决什么你要解决的问题对应的设计模式一句话解释我不想知道从哪拿依赖注入声明需求容器注入我不想知道怎么做门面模式只调入口内部编排两个模式经常一起出现Facade 需要依赖多个 Service → 这些 Service 通过 DI 注入到 Facade 的构造函数里。这就是接下来代码里要展示的。代码实现从 GitHub 开源项目看真实落地我从 GitHub 上找了两个真实的开源项目拆解 DI 和 Facade 的落地方式。阶段一依赖注入 —— FastAPI 的 dep 链对标 Spring 的 Autowired来看 ChiggyJain/PythonSystemDesignPattern 这个项目的 DI 链第一步定义数据库会话工厂app/core/database.pyfromsqlalchemy.ext.asyncioimportcreate_async_engine,async_sessionmaker enginecreate_async_engine(mysqlasyncmy://user:passlocalhost:3306/system_design_db,pool_size10,max_overflow20,)AsyncSessionLocalasync_sessionmaker(bindengine,expire_on_commitFalse)第二步搭建 DI 链条app/api/deps.pyfromfastapiimportDependsfromsqlalchemy.ext.asyncioimportAsyncSessionfromapp.core.databaseimportAsyncSessionLocalfromapp.repositories.user_repositoryimportUserRepositoryfromapp.services.user_serviceimportUserServiceasyncdefget_db():asyncwithAsyncSessionLocal()assession:yieldsessiondefget_user_repository(db:AsyncSessionDepends(get_db))-UserRepository:returnUserRepository(db)defget_user_service(repo:UserRepositoryDepends(get_user_repository))-UserService:returnUserService(repo)这条链的核心逻辑get_db→get_user_repository→get_user_service。每一层只依赖下一层的抽象FastAPI 的Depends()自动递归解析。第三步Service 层通过构造函数接收依赖app/services/user_service.pyclassUserService:def__init__(self,repository:UserRepository):self.repositoryrepository# 不 new靠注入asyncdefregister_user(self,email:str)-User:ifnotemail.endswith(company.com):raiseValueError(Only company emails allowed)existingawaitself.repository.get_by_email(email)ifexisting:raiseValueError(User already exists)userUser(iduuid4(),emailemail,is_activeTrue)returnawaitself.repository.save(user)第四步Controller 层拿到的已经是组装好的 Serviceapp/api/v1/users.pyrouter.post(/users)asyncdefcreate_user(payload:UserCreateRequest,service:UserServiceDepends(get_user_service),# 注入):userawaitservice.register_user(payload.email)returnuser这里和 Java 的写法做个直接对比// Java Spring 版本 —— 用 Autowired 注解ServicepublicclassUserService{privatefinalUserRepositoryrepository;publicUserService(UserRepositoryrepository){// 构造函数注入this.repositoryrepository;}publicUserregisterUser(Stringemail){// ... 业务逻辑}}RestControllerpublicclassUserController{AutowiredprivateUserServiceuserService;// 字段注入或构造函数注入PostMapping(/users)publicUsercreateUser(RequestBodyCreateRequestreq){returnuserService.registerUser(req.getEmail());}}对比下来Python FastAPI 和 Java Spring 的 DI 都围绕一个核心思想控制反转IoC——“不要找我我来找你”。Python 用函数式Depends()链Java 用注解 反射。殊途同归。阶段二门面模式 —— 一个入口编排五六个子系统接下来看这个项目里 Facade 的使用app/facades/exam_fee_facade.pyfromapp.repositories.fee_repositoryimportFeeRepositoryfromapp.services.fee_serviceimportFeeServicefromapp.queue.event_publisherimportEventPublisherfromapp.services.notification_serviceimportNotificationServicefromapp.factories.notification_factoryimportNotificationFactoryfromapp.strategies.notification_strategyimportNotificationStrategyclassExamFeeFacade: Facade hides all complexity of: - DB - Queue - Notification - Strategy Factory Adapter def__init__(self,fee_repo:FeeRepository):self.fee_serviceFeeService(fee_repofee_repo,publisherEventPublisher())self.notification_strategyNotificationStrategy()asyncdefpay_exam_fee(self,student_id:str,amount:float,):# 1️⃣ 核心业务操作feeawaitself.fee_service.submit_exam_fee(student_idstudent_id,amountamount)# 2️⃣ 决定通知方式channelself.notification_strategy.choose_channel(FEE_PAID)# 3️⃣ 异步通知adapter 处理重试 熔断adapterNotificationFactory.get_adapter(channel)notification_serviceNotificationService(adapter)awaitnotification_service.notify(tostudent_id,messagefExam fee paid:{amount})returnfee而 Controller 层的调用简洁到只剩一行有效代码app/api/v1/fees.pyrouter.post(/exam-fee)asyncdefpay_exam_fee(payload:FeeRequest,db:AsyncSessionDepends(get_db),):facadeExamFeeFacade(FeeRepository(db))feeawaitfacade.pay_exam_fee(student_idpayload.student_id,amountpayload.amount)return{status:success,fee_id:fee.id}你看 Controller 里面没有 DB 操作、没有定价逻辑、没有发通知的代码、没有选择渠道的策略——只有一个facade.pay_exam_fee()。这就是 Facade 的价值。再看一个更简单的例子来自 simon-das/design-patterns 的 facade.pyclassTicketingSystem:def__init__(self,movie_name):self.movie_namemovie_namedefbuy_ticket(self):print(fTicket purchased for{self.movie_name})classSeatReservationSystem:def__init__(self,theater_name):self.theater_nametheater_namedefselect_seat(self):print(fSeat selected at{self.theater_name})classSnackCounter:def__init__(self,snacks):self.snackssnacksdefbuy_snacks(self):print(f{self.snacks}purchased at the snack counter)classMovieTheaterFacade:def__init__(self,movie_name,theater_name,snacks):self.ticketing_systemTicketingSystem(movie_name)self.seat_reservation_systemSeatReservationSystem(theater_name)self.snack_counterSnackCounter(snacks)defwatch_movie(self):self.ticketing_system.buy_ticket()self.seat_reservation_system.select_seat()self.snack_counter.buy_snacks()print(Enjoy the movie!)if__name____main__:movie_theater_facadeMovieTheaterFacade(Movie-1,Theater-1,Burger and Coke)movie_theater_facade.watch_movie()用 Java 来做同样的 FacadepublicclassMovieTheaterFacade{privatefinalTicketingSystemticketingSystem;privatefinalSeatReservationSystemseatReservationSystem;privatefinalSnackCountersnackCounter;// DI 注入三个子系统publicMovieTheaterFacade(TicketingSystemticketingSystem,SeatReservationSystemseatReservationSystem,SnackCountersnackCounter){this.ticketingSystemticketingSystem;this.seatReservationSystemseatReservationSystem;this.snackCountersnackCounter;}publicvoidwatchMovie(){ticketingSystem.buyTicket();seatReservationSystem.selectSeat();snackCounter.buySnacks();System.out.println(Enjoy the movie!);}}// 使用时MovieTheaterFacadefacadenewMovieTheaterFacade(newTicketingSystem(Movie-1),newSeatReservationSystem(Theater-1),newSnackCounter(Burger and Coke));facade.watchMovie();Facade 不增加新功能——它只改变调用方式。原本调用方需要知道先买票、再选座、再买零食这个流程现在只需要知道看电影这一个入口。边界情况与陷阱DI Facade 用不好反而更糟看起来很完美了对吧但实际项目里踩过的坑比代码还多。陷阱一Facade 变成了上帝对象。门面把五六个子系统塞进一个类如果失控就会变成 3000 行的怪物。原则一个 Facade 只编排一个业务用例。上面例子只编排支付考试费不要顺手把退费对账也塞进去——各开各的 Facade。陷阱二循环依赖。Facade A 依赖 Service BService B 又依赖 Facade A——DI 容器直接炸。解法Facade 放在最顶层只能被 Controller 调用不能互相调用。陷阱三DI 层级过深。Depends(get_c)→Depends(get_b)→Depends(get_a)→ … 链太长一个依赖找不到整个链爆掉报错信息可能跟俄罗斯套娃一样难读。建议 DI 链控制在 3 层以内。陷阱四Java 开发者容易犯的——Autowired字段注入。// 坏字段注入AutowiredprivateUserServiceuserService;// 单元测试时没法 mockPython 里 FastAPI 的Depends()完美规避了这个问题——依赖必须通过函数参数传递天然支持 mock。高级考量DI Facade 与 Port-Adapter 的联动这个项目里还有一个值得注意的设计app/ports/notification_port.pyfromabcimportABC,abstractmethodclassNotificationPort(ABC):abstractmethodasyncdefsend(self,to:str,message:str)-None:pass这是Port-Adapter 模式也叫六边形架构与 DI 天然配合Controller → Facade → Service → Port(接口) ↑ Adapter(实现) ┌──┼──┐ SMS Email PushPort 接口Python 的ABC/ Java 的interfaceAdapter 具体实现SMS 适配器、邮件适配器DI 在运行时把 Adapter 注入到 ServiceJava 开发者对这个模式应该最熟悉——就是你们天天用的interfaceAutowired 多实现publicinterfaceNotificationPort{voidsend(Stringto,Stringmessage);}Component(sms)publicclassSmsAdapterimplementsNotificationPort{...}Component(email)publicclassEmailAdapterimplementsNotificationPort{...}ServicepublicclassNotificationService{AutowiredQualifier(sms)// 运行时决定注入哪个privateNotificationPortnotifier;}DI 让你不关心哪个实现Facade 让你不关心哪些步骤。两者配合就是企业级应用最核心的架构模式。对比表格Python vs Java 的 DI/Facade 实现维度Python (FastAPI)Java (Spring)核心区别DI 机制Depends()函数式依赖链Autowired 反射/代理Python 更显式Java 更隐式IoC 容器无需独立容器FastAPI 内置Spring IoC 容器ApplicationContextPython 轻量启动快接口抽象ABCabstractmethodinterface关键字Java 的接口是一等公民Facade 写法普通类 构造函数注入普通类 ServiceAutowiredPython 更简洁Java 更规范化Mock 测试直接传 mock 对象进去需要MockBean或MockitoPython 更直接无需框架辅助启动速度毫秒级无容器扫描秒级组件扫描Python 适合无服务/脚本场景编译期检查运行时报错编译期 运行时Java 有更强的类型安全保障面试追问比答案更深一层追问 1DI 和控制反转IoC到底有什么区别→ 回答方向IoC 是思想——“不要你来控制对象的创建把控制权交出去”。DI 是 IoC 的一种具体实现方式——“通过构造函数/Setter/接口把依赖传给你”。其他实现方式还有 Service Locator 模式。面试官想听你区分思想和手段。追问 2为什么说Facade 不新增功能但降低复杂度→ 回答方向Facade 做的事子系统的调用方本来也能做——但需要知道调用顺序、处理异常、编排流程。Facade 把这些流程知识封装了调用方的认知负担从N 个步骤降到1 个入口。复杂度没有消失只是从调用方转移到了 Facade 内部——这就是封装的价值。追问 3Python 的Depends()和 Spring 的Autowired哪个更好→ 回答方向没有绝对好坏。Depends()更显式依赖关系一眼看清但写起来啰嗦Autowired更便捷但依赖关系隐藏在注解里大规模项目里不知道这个 Bean 从哪来的是常见病。现代 Java 的最佳实践是构造函数注入Spring 4.3 对单构造函数的Autowired可省略既显式又方便。追问 4如果 Facade 里某个子系统挂了怎么办→ 回答方向这是 Facade 模式最容易被忽略的边界。有三种策略——①快速失败抛异常适合核心流程②降级跳过非必需步骤比如通知挂了不影响支付③异步重试消息队列 死信队列。上面的exam_fee_facade.py就用了策略③——NotificationStrategy 异步通知支付成功即使通知暂未送达也不算失败。总结DI 让你不用管谁来做Facade 让你不用管怎么做。读完这篇你应该能区分 DI 和 Facade 各自的职责边界在项目里用构造函数注入替代满屏的new为一个复杂业务流程设计 Facade把 Controller 瘦成一行有效代码在面试时说出DI 是 IoC 的一种实现而不是DI IoC理解 Python FastAPI 的Depends()和 Java Spring 的Autowired背后的同一套思想——控制反转不是让你失去控制是让你只控制你该控制的。