
概念
外观模式(Facade Pattern) 是一种使用频率非常高的结构型设计模式,它通过引入一个外观角色来简化客户端与子系统之间的交互,为复杂的子系统调用提供一个统一的入口,降低子系统与客户端的耦合度,且客户端调用非常方便。
定义:
为子系统中的一组接口提供一个统一的入口。外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
外观模式又称为门面模式,它是一种对象结构型模式。外观模式是迪米特法则的一种具体实现,通过引入一个新的外观角色可以降低原有系统的复杂度,同时降低客户类与子系统的耦合度。
[!tip]
我们可以通过这样一个生活的例子理解外观模式:
- 我们在生活中使用的智能洗衣机,外观模式就是提供了多个功能,用户只需选择一个模式,按下启动键,洗衣机内部就会自动协调所有子系统,按预设程序完成
外观模式的结构

外观模式包含如下两个角色:
外观(Facade):提供了一种访问特定子系统功能的便捷方式, 其了解如何重定向客户端请求,知晓如何操作一切活动部件。附加外观 (Additional Facade):可以避免多种不相关的功能污染单一外观, 使其变成又一个复杂结构。 客户端和其他外观都可使用附加外观。复杂子系统 (Complex Subsystem):由数十个不同对象构成。 如果要用这些对象完成有意义的工作, 你必须深入了解子系统的实现细节, 比如按照正确顺序初始化对象和为其提供正确格式的数据。子系统类不会意识到外观的存在, 它们在系统内运作并且相互之间可直接进行交互。客户端 (Client):使用外观代替对子系统对象的直接调用。
外观模式的实现
类图设计
下面是一个矮人挖金矿的过程,使用Facade模式来演示其流程

代码实现
- 复杂子系统
1public abstract class DwarfMineWorker { 2 public abstract String name(); 3 public abstract void work(); 4 public void goToSleep() { 5 System.out.println(name() + ":哈哈哈,彩票中了1个亿,明天当场辞职。"); 6 } 7 public void wakeUp() { 8 System.out.println(name() + ":万恶的东东哥,就知道催,都还没有睡醒。"); 9 } 10 public void goHome() { 11 System.out.println(name() + ":终于可以回家了,结束了被剥削的一天。"); 12 } 13 public void goToMine() { 14 System.out.println(name() + ":别催了,我到矿场了。"); 15 } 16 private void action(Action action) { 17 switch (action) { 18 case WAKE_UP: 19 wakeUp(); 20 break; 21 case GO_TO_MINE: 22 goToMine(); 23 break; 24 case WORK: 25 work(); 26 break; 27 case GO_HOME: 28 goHome(); 29 break; 30 case GO_TO_SLEEP: 31 goToSleep(); 32 break; 33 default: 34 System.out.println(name() + "不加钱,休想。"); 35 break; 36 } 37 } 38 public void action(Action... actions) { 39 Arrays.stream(actions).forEach(this::action); 40 } 41 enum Action { 42 GO_TO_SLEEP, 43 WAKE_UP, 44 GO_HOME, 45 GO_TO_MINE, 46 WORK; 47 } 48}
1public class DwarfCartOperator extends DwarfMineWorker { 2 @Override 3 public String name() { 4 return "矿车手——小图"; 5 } 6 @Override 7 public void work() { 8 System.out.println(name() + ":这一车运完休息一下吧!"); 9 } 10}
1public class DwarfGoldDigger extends DwarfMineWorker { 2 @Override 3 public String name() { 4 return "挖矿者——小超"; 5 } 6 @Override 7 public void work() { 8 System.out.println(name() + ":挖呀挖呀,快10T了吧!"); 9 } 10}
1public class DwarfTunnelDigger extends DwarfMineWorker { 2 @Override 3 public String name() { 4 return "矿道工——小海"; 5 } 6 @Override 7 public void work() { 8 System.out.println(name() + ":先把矿道维修好了再去修新的矿道吧!"); 9 } 10}
- 外观
1import java.util.Arrays; 2import java.util.List; 3public class DwarfGoldMineFacade { 4 private final List<DwarfMineWorker> workers; 5 public DwarfGoldMineFacade() { 6 workers = Arrays.asList( 7 new DwarfTunnelDigger(), // 小海 8 new DwarfGoldDigger(), // 小超 9 new DwarfCartOperator() // 小图 10 ); 11 } 12 public void startNewDay() { 13 workers.forEach((worker) -> 14 worker.action(DwarfMineWorker.Action.WAKE_UP, DwarfMineWorker.Action.GO_TO_MINE) 15 ); 16 } 17 public void digOutGold() { 18 workers.forEach((worker) -> 19 worker.action(DwarfMineWorker.Action.WORK) 20 ); 21 } 22 public void endDay() { 23 workers.forEach((worker) -> 24 worker.action(DwarfMineWorker.Action.GO_HOME, DwarfMineWorker.Action.GO_TO_SLEEP) 25 ); 26 } 27}
- 客户端
1public class Client { 2 public static void main(String[] args) { 3 DwarfGoldMineFacade facade = new DwarfGoldMineFacade(); 4 // 通过外观类一键开启矿工的一天 5 facade.startNewDay(); 6 System.out.println("--- 休息时间结束,开始干活 ---"); 7 facade.digOutGold(); 8 System.out.println("--- 下班时间到 ---"); 9 facade.endDay(); 10 } 11}

TIP: 在标准的外观模式结构图中,如果需要增加、删除或更换与外观类交互的子系统类,必须修改外观类或客户端的源代码,这将违背开闭原则,因此可以通过引入**抽象外观类**来对系统进行改进,在一定程度上可以解决该问题。
例如:子系统使用的是阿里云服务,但是第二版需要变成腾讯云,那我们可以怎么改?
- 使用抽象外观,一个子类实现阿里云,一个子类实现腾讯云
外观模式适用环境
在项目开发中, 如果我们需要一个其他人开发的类, 但是还没有开发好, 那么我们也可以使用外观类来写点测试数据先运行先, 等到其他人开发好后, 直接在外观类进行替换, 这样就不需要修改我们已经写好的代码了.
举个例子:
假设你此时需要对接同时写的一个支付接口,但是同时还没写完
- 定义外观类
1public class PaymentFacade { 2 public boolean pay(double amount) { 3 // 先在这里写死,模拟支付永远成功 4 System.out.println("【临时调试】正在使用外观类模拟支付:" + amount + "元"); 5 return true; 6 } 7}
- 业务代码
1public class OrderService { 2 //引入外观类 3 private PaymentFacade paymentFacade = new PaymentFacade(); 4 public void completeOrder() { 5 //这里假设逻辑已经写好了,直接调用外观类 6 if (paymentFacade.pay(100.0)) { 7 System.out.println("订单处理成功!"); 8 } 9 } 10}
- 同事写好之后,改一下外观类
1public class PaymentFacade { 2 // 引入同事写好的支付接口 3 private RealBankService realService = new RealBankService(); 4 public boolean pay(double amount) { 5 // 将请求转发给真实的子系统 6 // 哪怕同事的方法名和你起的不一样,也可以在这里转化掉 7 return realService.executeTransaction(amount, "CNY"); 8 } 9}业务代码保持不变,无需改动
主要优点
- 它对客户端屏蔽了子系统组件,减少了客户端所需处理的对象数目,并使得子系统使用起来更加容易。通过引入外观模式,客户端代码将变得很简单,与之关联的对象也很少。
- 它实现了子系统与客户端之间的松耦合关系,这使得子系统的变化不会影响到调用它的客户端,只需要调整外观类即可。
- 一个子系统的修改对其他子系统没有任何影响,而且子系统内部变化也不会影响到外观对象。
主要缺点
- 不能很好地限制客户端直接使用子系统类,如果对客户端访问子系统类做太多的限制则减少了可变性和灵活性。(外观类只用两个,子系统有10个)
- 如果设计不当,增加新的子系统可能需要修改外观类的源代码,违背了开闭原则。(解决:抽象外观类)
适用环境
在以下情况下可以考虑使用外观模式:
- 当要为访问一系列复杂的子系统提供一个简单入口时可以使用外观模式。
- 客户端程序与多个子系统之间存在很大的依赖性。引入外观类可以将子系统与客户端解耦,从而提高子系统的独立性和可移植性。
- 在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系,而通过外观类建立联系,降低层之间的耦合度。
如果这篇文章对你有帮助,欢迎点赞、评论、关注、收藏。你们的支持是我前进的动力!
《【从0开始学设计模式-11| 外观模式】》 是转载文章,点击查看原文。