news 2026/6/5 20:56:46

GoF设计模式——外观模式

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
GoF设计模式——外观模式

本文是【GoF设计模式】系列第9篇

前言

为什么需要外观模式?

开发中经常遇到这种情况:一个业务操作需要依次调用多个子系统。比如"启动系统"要先初始化数据库连接、加载配置文件、启动缓存服务、注册定时任务——每个子系统都有自己的接口和调用顺序,漏掉一步或顺序错误都会导致系统异常。

如果让客户端直接操作这些子系统,代码会变成这样:

// 客户端需要了解所有子系统的细节databaseService.init(config.getDbUrl(),config.getDbUser());configService.load("application.properties");cacheService.start(config.getCacheNodes());schedulerService.registerJobs();

一旦子系统数量增加或调用顺序调整,所有使用的地方都要改。这就是外观模式要解决的问题——为复杂的子系统提供一个简单的统一入口。

概念

外观模式(Facade Pattern)是一种结构型设计模式,核心思想是为子系统中的一组接口提供一个统一的高层接口,使子系统更容易使用

外观模式定义了一个更高层次的接口,让子系统更加容易使用。客户端只需要调用外观类的方法,而不需要了解子系统内部的复杂交互。这就好比去餐厅吃饭,只需要跟服务员点菜(外观),不需要自己去厨房告诉厨师怎么做、去仓库拿食材、去洗碗间拿餐具——这些子系统的复杂性都被服务员这个"外观"屏蔽了。

外观模式的结构比较简单,只包含两个角色:

  • 外观(Facade):对外提供一个统一的高层次接口,使复杂的子系统变得更易使用。外观类知道哪些子系统负责处理请求,将客户端的请求委派给对应的子系统对象
  • 子系统类(Subsystem):实现子系统的功能,处理外观类指派的任务。子系统类不感知外观的存在,对子系统而言外观只是另一个客户端

持有

持有

持有

依赖

Facade

-subsystemA: SubsystemA

-subsystemB: SubsystemB

-subsystemC: SubsystemC

+operation()

SubsystemA

+operationA()

SubsystemB

+operationB()

SubsystemC

+operationC()

Client

Client 只依赖 Facade,完全不感知 SubsystemA/B/C 的存在。Facade 内部协调调用多个子系统的方法,将复杂的交互逻辑封装在内部。

实现

基础实现

外观模式的基本实现分为以下几个步骤:

  1. 定义子系统类,实现各自的业务功能
  2. 定义外观类,持有子系统对象的引用,提供统一的简化接口
  3. 外观类的方法内部协调调用多个子系统的方法
  4. 客户端通过外观类访问子系统,无需了解子系统内部细节
// 子系统AclassSubsystemA{publicvoidoperationA(){System.out.println("SubsystemA operation");}}// 子系统BclassSubsystemB{publicvoidoperationB(){System.out.println("SubsystemB operation");}}// 子系统CclassSubsystemC{publicvoidoperationC(){System.out.println("SubsystemC operation");}}// 外观类classFacade{privateSubsystemAa;privateSubsystemBb;privateSubsystemCc;publicFacade(){a=newSubsystemA();b=newSubsystemB();c=newSubsystemC();}// 简化接口:客户端只需调用这一个方法publicvoidoperation(){a.operationA();b.operationB();c.operationC();}}// 客户端代码Facadefacade=newFacade();facade.operation();// 一次调用,完成三个子系统的操作

引入一个例子:「去餐厅吃饭,客人只和服务员打交道。服务员接到点菜请求后,会依次通知厨房做菜、仓库拿食材、洗碗间准备餐具——这些子系统的协作过程对客人完全透明」。

餐厅对应 Facade(外观),厨房/仓库/洗碗间对应 Subsystem(子系统),客人对应 Client——服务员封装了三个子系统的协作流程,客人只需要说"点菜",后续一切自动完成。

// 子系统:厨房classKitchen{publicvoidcook(Stringdish){System.out.println("厨房正在制作: "+dish);}}// 子系统:仓库classWarehouse{publicStringgetIngredients(Stringdish){System.out.println("仓库正在配送食材: "+dish);return"食材-"+dish;}}// 子系统:洗碗间classDishwashing{publicvoidprepareUtensils(){System.out.println("洗碗间正在准备餐具");}}// 外观类:服务员classWaiterFacade{privateKitchenkitchen;privateWarehousewarehouse;privateDishwashingdishwashing;publicWaiterFacade(){this.kitchen=newKitchen();this.warehouse=newWarehouse();this.dishwashing=newDishwashing();}// 简化接口:客人只需说"点菜"publicvoidorderDish(Stringdish){// 外观内部协调子系统dishwashing.prepareUtensils();Stringingredients=warehouse.getIngredients(dish);kitchen.cook(dish);System.out.println("菜品 "+dish+" 已准备好,请享用!");}}// 客人(客户端)只和服务员打交道WaiterFacadewaiter=newWaiterFacade();waiter.orderDish("红烧肉");// 一行代码,背后三个子系统协作完成

外观模式最大的好处是简化:客户端不需要知道子系统有多少个、调用顺序是什么、每个子系统需要什么参数——所有复杂性都被外观封装了。

总结

外观模式本质上是一层"统一入口"——将多个子系统的复杂交互封装成一个简单的接口。

什么时候用

  • 想为复杂的子系统提供一个简单的统一入口
  • 客户端与多个子系统之间存在强耦合,需要降低依赖
  • 分层架构中,需要为每一层定义清晰的入口点

什么时候不用

  • 子系统本身就很简单,不需要额外的封装层
  • 客户端需要细粒度控制子系统,外观会限制灵活性
  • 外观类可能变成"上帝类",承担过多职责

简单记忆

外观解决"调用复杂"的问题,是给子系统"开一个统一窗口"。能直接调用子系统时,不必强行加外观。

相似模式区分

外观模式与其他结构型模式在"包装对象"这一结构上相似,但意图完全不同。

模式接口关系核心意图典型场景
外观目标接口是新设计的简化复杂子系统的调用JdbcTemplate、SLF4J
适配器目标接口 ≠ 被包装对象接口转换接口,让不兼容的类协同第三方SDK接入、Java I/O
装饰器目标接口 = 被包装对象接口增强功能,接口不变Java I/O流嵌套
代理目标接口 = 被包装对象接口控制访问,附加访问前后逻辑Spring AOP、MyBatis Mapper

外观 vs 中介者

维度外观模式中介者模式
核心意图为子系统提供统一接口,简化外部访问协调多个对等对象之间的交互
结构差异外观是子系统的"门面",子系统不感知外观中介者是对象间的"协调者",对象感知中介者
关注点简化调用方的使用体验解耦多个对象之间的网状依赖
典型场景为复杂子系统提供简单APIGUI组件间交互、多对象协作

逐步区分法

  • 如果目的是简化子系统的调用方式,子系统之间不需要互相通信 → 选外观模式
  • 如果目的是协调多个对等对象之间的交互,对象之间需要互相感知 → 选中介者模式
  • 如果子系统不感知外观的存在,外观只是单向调用 → 选外观模式
  • 如果对象通过中介者双向通信,中介者协调各方 → 选中介者模式

外观 vs 代理

维度外观模式代理模式
核心意图简化对多个子系统的访问,提供新接口控制对真实对象的访问,保持相同接口
结构差异外观定义新接口,子系统有独立接口代理与真实对象实现同一接口
关注点降低使用复杂度控制访问(延迟加载、权限校验、缓存等)
典型场景封装复杂流程为单一调用远程代理、虚拟代理、保护代理

逐步区分法

  • 如果需要封装多个子系统的复杂调用,提供更简单的接口 → 选外观模式
  • 如果需要控制对单个对象的访问(延迟、权限、远程) → 选代理模式
  • 如果接口是新定义的,与子系统不同 → 选外观模式
  • 如果接口与真实对象一致,只是增强访问控制 → 选代理模式

外观 vs 适配器

维度外观模式适配器模式
核心意图简化接口,让子系统更易用接口转换,让不兼容的类协同工作
结构差异外观提供新接口,子系统不变适配器将已有接口转换为目标接口
关注点降低使用门槛解决不兼容问题
典型场景为复杂子系统提供统一入口集成第三方库、旧系统对接

逐步区分法

  • 如果子系统接口已经兼容,只是调用复杂 → 选外观模式
  • 如果子系统接口不兼容,需要转换才能协同工作 → 选适配器模式
  • 如果目的是简化调用,减少客户端代码量 → 选外观模式
  • 如果目的是复用已有类,但接口不匹配 → 选适配器模式

练习题目

智能家居控制系统

题目描述:小明有一个智能家居系统,包含四个子系统:

  • 照明系统(Light):开灯、关灯
  • 空调系统(AirConditioner):开启、关闭、设置温度
  • 安防系统(SecuritySystem):布防、撤防
  • 窗帘系统(Curtain):打开、关闭

每天出门时,小明需要依次:关灯 → 关空调 → 关窗帘 → 安防布防。
每天回家时,小明需要依次:安防撤防 → 开灯 → 开空调并设置温度为26度 → 开窗帘。

请使用外观模式设计智能家居控制台,提供leaveHome()goHome()两个简化接口,让小明一键操作。

输入描述:第一行是一个整数 N(1 ≤ N ≤ 100),表示后续有 N 条指令。接下来的 N 行,每行一个字符串:leave表示离家,go表示回家。

输出描述:按顺序输出每条指令触发的子系统操作结果。

输入示例

3 leave go leave

输出示例

Light is turned off. AirConditioner is turned off. Curtain is closed. SecuritySystem is armed. === Left Home === SecuritySystem is disarmed. Light is turned on. AirConditioner is turned on, temperature set to 26. Curtain is opened. === Went Home === Light is turned off. AirConditioner is turned off. Curtain is closed. SecuritySystem is armed. === Left Home ===

解题思路:智能家居系统是外观模式的典型应用。四个子系统分别控制灯、空调、窗帘和安防,如果不使用外观模式,小明每次出门/回家都要依次调用四个子系统的方法,一旦某个子系统新增了操作步骤,所有调用的地方都要修改。外观类SmartHomeFacade将出门和回家的操作封装成两个简化接口,客户端不需要了解各子系统的调用顺序和细节。

importjava.util.*;publicclassMain{publicstaticvoidmain(String[]args){Scannersc=newScanner(System.in);intn=sc.nextInt();SmartHomeFacadefacade=newSmartHomeFacade();while(n-->0){Stringop=sc.next();if("leave".equals(op)){facade.leaveHome();}else{facade.goHome();}}}}// 照明子系统classLightSystem{publicvoidturnOn(){System.out.println("Light is turned on.");}publicvoidturnOff(){System.out.println("Light is turned off.");}}// 空调子系统classAirConditionerSystem{publicvoidturnOn(inttemperature){System.out.println("AirConditioner is turned on, temperature set to "+temperature+".");}publicvoidturnOff(){System.out.println("AirConditioner is turned off.");}}// 安防子系统classSecuritySystem{publicvoidarm(){System.out.println("SecuritySystem is armed.");}publicvoiddisarm(){System.out.println("SecuritySystem is disarmed.");}}// 窗帘子系统classCurtainSystem{publicvoidopen(){System.out.println("Curtain is opened.");}publicvoidclose(){System.out.println("Curtain is closed.");}}// 外观类:封装智能家居的简化操作classSmartHomeFacade{privateLightSystemlight;privateAirConditionerSystemairConditioner;privateSecuritySystemsecurity;privateCurtainSystemcurtain;publicSmartHomeFacade(){this.light=newLightSystem();this.airConditioner=newAirConditionerSystem();this.security=newSecuritySystem();this.curtain=newCurtainSystem();}publicvoidleaveHome(){light.turnOff();airConditioner.turnOff();curtain.close();security.arm();System.out.println("=== Left Home ===");}publicvoidgoHome(){security.disarm();light.turnOn();airConditioner.turnOn(26);curtain.open();System.out.println("=== Went Home ===");}}

扩展:实际项目中的外观模式

Spring 的 JdbcTemplate

原生 JDBC 操作非常繁琐——获取连接、创建 Statement、执行 SQL、处理结果集、关闭连接,每一步都要处理异常和资源释放。Spring 的JdbcTemplate就是外观模式的典型应用,它将这些步骤封装成一个简单的方法调用。

JdbcTemplatejdbcTemplate=newJdbcTemplate(dataSource);List<Map<String,Object>>users=jdbcTemplate.queryForList("SELECT * FROM user WHERE age > ?",18);

开发者只需要关注 SQL 和参数,如果不用JdbcTemplate,同样功能的代码量要多 3-5 倍。这就是外观模式"简化调用"的体现。

SLF4J 日志门面

Java 生态中有多种日志实现(Log4j、Logback、java.util.logging 等),如果业务代码直接依赖某个具体实现,切换日志框架时需要修改大量代码。SLF4J 就是一个外观,为所有日志框架提供统一接口。

privatestaticfinalLoggerlogger=LoggerFactory.getLogger(OrderService.class);logger.info("创建订单: {}",orderId);

SLF4J 本身不实现日志功能,它只是外观接口。底层可以无缝切换 Logback、Log4j2 等实现,业务代码零修改。

Spring Security 的 SecurityContextHolder

在 Web 应用中获取当前登录用户信息,涉及到 SecurityContext、Authentication、UserDetails 等多个类的交互。Spring Security 提供了简化的外观接口:

Stringusername=SecurityContextHolder.getContext().getAuthentication().getName();

一行代码,背后涉及从 Session/Cookie 中获取上下文、认证管理器、用户详情服务等多个子系统的协作。开发者不需要关心这些细节。

MyBatis 的 SqlSession

MyBatis 的SqlSession是对 JDBC 操作的外观封装,一个方法调用背后可能涉及连接获取、SQL 解析、参数映射、结果集映射、事务管理等多个子系统的协作。

SqlSessionsession=sqlSessionFactory.openSession();Useruser=session.selectOne("com.example.mapper.UserMapper.selectById",1L);session.commit();session.close();

SqlSession将 Executor、StatementHandler、ParameterHandler、ResultSetHandler 等组件封装成简单的 API。

文件上传服务

文件上传通常涉及多个步骤——文件校验、存储、元数据记录、URL 生成等。通过外观类将这些步骤封装成一个简单的方法,Controller 只需要一行调用:

FileUploadFacadefacade=newFileUploadFacade();Stringurl=facade.upload(file);

如果没有外观类,Controller 中需要注入三个服务,按顺序调用并处理异常,代码会非常冗长。

支付网关

在线支付涉及多个步骤——创建支付单、调用第三方支付接口、更新订单状态、发送通知等。通过外观类将这些步骤封装,业务方只需要调用一个支付方法:

PaymentFacadepaymentFacade=newPaymentFacade();booleansuccess=paymentFacade.pay("ORDER_001",newBigDecimal("99.99"),"WECHAT");

支付流程涉及 4 个子系统的协作,外观类将这些复杂性封装,Controller 只需要关注"发起支付"这一个动作。

现在可能还用不到这些,但等到需要接入复杂子系统、封装繁琐流程的时候,会突然发现:“这不就是外观模式吗?”——那时候就真的懂了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/5 20:55:49

ChanlunX缠论插件终极指南:3步实现通达信自动缠论技术分析

ChanlunX缠论插件终极指南&#xff1a;3步实现通达信自动缠论技术分析 【免费下载链接】ChanlunX 缠中说禅炒股缠论可视化插件 项目地址: https://gitcode.com/gh_mirrors/ch/ChanlunX 你是否曾经为复杂的缠论分析感到头疼&#xff1f;面对K线图上密密麻麻的走势&#x…

作者头像 李华
网站建设 2026/6/5 20:54:34

告别Excel手忙脚乱:用R的tidyverse包5分钟搞定GSEA分析前的基因数据整理

基因数据分析革命&#xff1a;tidyverse赋能GSEA预处理全流程实战在生物信息学领域&#xff0c;GSEA&#xff08;基因集富集分析&#xff09;已成为揭示基因功能关联的重要工具。但许多研究者往往在分析的第一步——数据预处理环节就遭遇瓶颈。传统Excel手工操作不仅效率低下&a…

作者头像 李华
网站建设 2026/6/5 20:53:11

AI再聪明,也看不见你板子上的那根飞线

纯应用层开发里&#xff0c;很多问题会以比较清楚的方式出现&#xff1a;接口报错、日志堆栈、异常码、请求超时、数据库连接失败。虽然也复杂&#xff0c;但至少系统愿意给你一些文字证据。嵌入式软件不一样。它经常只给你一个现象。UART 偶发丢一帧。I2C 总线偶尔挂死。ADC 数…

作者头像 李华
网站建设 2026/6/5 20:53:04

电子元器件采购进阶:从询价到供应链管理的核心策略

1. 从“询价”到“找对象”&#xff1a;采购思维的进阶很多刚入行的朋友&#xff0c;或者是从技术转采购的工程师&#xff0c;最容易犯的一个错误就是&#xff1a;拿到一个料号&#xff0c;第一反应就是群发邮件或者打一圈电话&#xff0c;开口就问“这个芯片多少钱&#xff1f…

作者头像 李华
网站建设 2026/6/5 20:52:09

从PN结到LED驱动:二极管原理与电流控制实践

1. 从单向导电到发光&#xff1a;二极管的核心原理与工程实践在电子工程师的日常里&#xff0c;二极管可能是最不起眼却又无处不在的元件。它不像微处理器那样能执行复杂指令&#xff0c;也不像大功率晶体管那样能驱动重型负载&#xff0c;但它却是构建一切电子逻辑的基石。我刚…

作者头像 李华
网站建设 2026/6/5 20:52:00

为什么你会觉得AI离你很远?

先讲一个真实的故事。 昨天我妈突然问我&#xff1a;“那个什么AI&#xff0c;是不是只有你们搞技术的才用得上&#xff1f;” 我愣了一下。因为她每天刷的短视频推荐、手机里的美颜滤镜、甚至买菜软件的"猜你喜欢"——全是AI。她用了好几年&#xff0c;却完全不知道…

作者头像 李华