GoF设计模式——外观模式

发布时间:2026/6/5 20:56:50

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: SubsystemCoperation()SubsystemAoperationA()SubsystemBoperationB()SubsystemCoperationC()ClientClient 只依赖 Facade完全不感知 SubsystemA/B/C 的存在。Facade 内部协调调用多个子系统的方法将复杂的交互逻辑封装在内部。实现基础实现外观模式的基本实现分为以下几个步骤定义子系统类实现各自的业务功能定义外观类持有子系统对象的引用提供统一的简化接口外观类的方法内部协调调用多个子系统的方法客户端通过外观类访问子系统无需了解子系统内部细节// 子系统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(){anewSubsystemA();bnewSubsystemB();cnewSubsystemC();}// 简化接口客户端只需调用这一个方法publicvoidoperation(){a.operationA();b.operationB();c.operationC();}}// 客户端代码FacadefacadenewFacade();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.kitchennewKitchen();this.warehousenewWarehouse();this.dishwashingnewDishwashing();}// 简化接口客人只需说点菜publicvoidorderDish(Stringdish){// 外观内部协调子系统dishwashing.prepareUtensils();Stringingredientswarehouse.getIngredients(dish);kitchen.cook(dish);System.out.println(菜品 dish 已准备好请享用);}}// 客人客户端只和服务员打交道WaiterFacadewaiternewWaiterFacade();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()两个简化接口让小明一键操作。输入描述第一行是一个整数 N1 ≤ 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){ScannerscnewScanner(System.in);intnsc.nextInt();SmartHomeFacadefacadenewSmartHomeFacade();while(n--0){Stringopsc.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.lightnewLightSystem();this.airConditionernewAirConditionerSystem();this.securitynewSecuritySystem();this.curtainnewCurtainSystem();}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就是外观模式的典型应用它将这些步骤封装成一个简单的方法调用。JdbcTemplatejdbcTemplatenewJdbcTemplate(dataSource);ListMapString,ObjectusersjdbcTemplate.queryForList(SELECT * FROM user WHERE age ?,18);开发者只需要关注 SQL 和参数如果不用JdbcTemplate同样功能的代码量要多 3-5 倍。这就是外观模式简化调用的体现。SLF4J 日志门面Java 生态中有多种日志实现Log4j、Logback、java.util.logging 等如果业务代码直接依赖某个具体实现切换日志框架时需要修改大量代码。SLF4J 就是一个外观为所有日志框架提供统一接口。privatestaticfinalLoggerloggerLoggerFactory.getLogger(OrderService.class);logger.info(创建订单: {},orderId);SLF4J 本身不实现日志功能它只是外观接口。底层可以无缝切换 Logback、Log4j2 等实现业务代码零修改。Spring Security 的 SecurityContextHolder在 Web 应用中获取当前登录用户信息涉及到 SecurityContext、Authentication、UserDetails 等多个类的交互。Spring Security 提供了简化的外观接口StringusernameSecurityContextHolder.getContext().getAuthentication().getName();一行代码背后涉及从 Session/Cookie 中获取上下文、认证管理器、用户详情服务等多个子系统的协作。开发者不需要关心这些细节。MyBatis 的 SqlSessionMyBatis 的SqlSession是对 JDBC 操作的外观封装一个方法调用背后可能涉及连接获取、SQL 解析、参数映射、结果集映射、事务管理等多个子系统的协作。SqlSessionsessionsqlSessionFactory.openSession();Userusersession.selectOne(com.example.mapper.UserMapper.selectById,1L);session.commit();session.close();SqlSession将 Executor、StatementHandler、ParameterHandler、ResultSetHandler 等组件封装成简单的 API。文件上传服务文件上传通常涉及多个步骤——文件校验、存储、元数据记录、URL 生成等。通过外观类将这些步骤封装成一个简单的方法Controller 只需要一行调用FileUploadFacadefacadenewFileUploadFacade();Stringurlfacade.upload(file);如果没有外观类Controller 中需要注入三个服务按顺序调用并处理异常代码会非常冗长。支付网关在线支付涉及多个步骤——创建支付单、调用第三方支付接口、更新订单状态、发送通知等。通过外观类将这些步骤封装业务方只需要调用一个支付方法PaymentFacadepaymentFacadenewPaymentFacade();booleansuccesspaymentFacade.pay(ORDER_001,newBigDecimal(99.99),WECHAT);支付流程涉及 4 个子系统的协作外观类将这些复杂性封装Controller 只需要关注发起支付这一个动作。现在可能还用不到这些但等到需要接入复杂子系统、封装繁琐流程的时候会突然发现“这不就是外观模式吗”——那时候就真的懂了。

相关新闻