天平山上白云泉,云自无心水自闲
先用两个图来表示一下外观模式产生的原因:
使用外观模式之后:
可以明显的看到,使用外观模式之后,就可以让外部减少与子系统内多个模块的交互,松散耦合,从而让外部能够更简单地使用子系统
外观模式(Facade Pattern):外部与一个子系统的通信必须通过一个 统一的外观对象 进行,为子系统中的一组接口 提供一个一致的界面,外观模式定义了一个高层接口,这个接口 使得这一子系统更加容易使用 。外观模式又称为 门面模式 ,它是一种 对象结构型模式。
先来解释一下两个词:
- 界面:这里提到的界面,主要指的是从一个组件外部来看这个组件,能够看到什么,这就是这个组件的界面,也就是所说的外观。
- 接口:这里提到的接口,主要指的是外部和内部交互的一个通道,也就是客户端和被访问的系统之间的一个通道,并不只局限于我们平时所说的接口interface,还可以是方法。
模式结构:
- Facade:外观角色: 定义子系统的多个模块对外的高层接口,通常需要调用内部多个模块,从而把客户的请求代理给适当的子系统对象。
- Module:子系统角色:实现系统的部分功能,客户可以通过外观角色访问它,各个模块之间可能有交互。
For Example
场景:你考上了大学,开学了,觉得笔记本电脑打游戏不够拉风,准备在宿舍装一个台式电脑,你决定先自己试着装一下,于是要去电子市场里买CPU、主板、显示屏,但由于你对电脑一窍不通,自己组装虽然省钱,但属实麻烦,不如找一个安装电脑公司(外观),这样你只需要最后拿电脑就行了。
电子市场,里面有CPU、主板、显示屏:
package com.ekin.softwaredesign.facade;
/**
* @Author: ekin
* @Date: 2021/4/11 18:29
*/
public class Emarket {
private String cpu;
private String screen;
private String mainBoard ;
public Emarket() {
}
public Emarket(String cpu, String screen, String mainBoard) {
this.cpu = cpu;
this.screen = screen;
this.mainBoard = mainBoard;
}
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public String getScreen() {
return screen;
}
public void setScreen(String screen) {
this.screen = screen;
}
public String getMainBoard() {
return mainBoard;
}
public void setMainBoard(String mainBoard) {
this.mainBoard = mainBoard;
}
}
安装CPU服务:
package com.ekin.softwaredesign.facade;
/**
* @Author: ekin
* @Date: 2021/4/11 18:33
*/
public class CpuService {
public boolean installCpu(Emarket emarket){
System.out.println("安装CPU服务");
return true;
}
}
安装显示屏服务:
package com.ekin.softwaredesign.facade;
/**
* @Author: ekin
* @Date: 2021/4/11 18:35
*/
public class ScreenService {
public boolean installScreen(Emarket emarket){
System.out.println("安装显示屏服务");
return true;
}
}
安装主板服务:
package com.ekin.softwaredesign.facade;
/**
* @Author: ekin
* @Date: 2021/4/11 18:36
*/
public class MainBoardService {
public boolean installBoard(Emarket emarket){
System.out.println("安装主板服务");
return true;
}
}
电脑组装公司(外观类):
package com.ekin.softwaredesign.facade;
/**
* @Author: ekin
* @Date: 2021/4/11 18:46
*/
public class ComputerCompany {
private CpuService cpuService;
private MainBoardService mainBoardService;
private ScreenService screenService;
public CpuService getCpuService() {
return cpuService;
}
public void setCpuService(CpuService cpuService) {
this.cpuService = cpuService;
}
public MainBoardService getMainBoardService() {
return mainBoardService;
}
public void setMainBoardService(MainBoardService mainBoardService) {
this.mainBoardService = mainBoardService;
}
public ScreenService getScreenService() {
return screenService;
}
public void setScreenService(ScreenService screenService) {
this.screenService = screenService;
}
public void computerCompany(Emarket emarket){
if(CpuService.installCpu(emarket)){
if (ScreenService.installScreen(emarket)) {
if (MainBoardService.installBoard(emarket)) {
System.out.println("电脑组装完成,打游戏起飞~");
}
}
}
}
}
测试类:
package com.ekin.softwaredesign.facade;
/**
* @Author: ekin
* @Date: 2021/4/11 18:54
*/
public class Test {
public static void main(String[] args) {
Emarket emarket = new Emarket("cpu","screen","mainBoard");
ComputerCompany computerCompany = new ComputerCompany();
computerCompany.computerCompany(emarket);
}
}
UML图:
只与外观类交互:
package com.ekin.softwaredesign.facade;
/**
* @Author: ekin
* @Date: 2021/4/11 18:46
*/
public class ComputerCompany {
private CpuService cpuService = new CpuService();
private MainBoardService mainBoardService = new MainBoardService();
private ScreenService screenService = new ScreenService();
public void computerCompany(Emarket emarket){
if(CpuService.installCpu(emarket)){
if(ScreenService.installScreen(emarket)){
if (MainBoardService.installBoard(emarket)) {
System.out.println("电脑组装完成,打游戏起飞~");
}
}
}
}
}
UML:
调用顺序示意图:
总结
- 可以看到外观模式的本质是封装交互,简化调用,外观封装了子系统外部和子系统内部多个模块的交互过程,从而简化了外部的调用。通过外观,子系统为外部提供一些高层的接口,以方便它们的使用
- 体现了“最少知道原则”
优点:
- 松散耦合:外观模式松散了客户端与子系统的耦合关系,让子系统内部的模块能更容易扩展和维护。
- 简单易用:外观模式让子系统更加易用,客户端不再需要了解子系统内部的实现,也不需要跟众多子系统内部的模块进行交互,只需要跟外观交互就可以了,相当于外观类为外部客户端使用子系统提供了一站式服务。
- 更好地划分访问的层次:通过合理使用Facade,可以帮助我们更好地划分访问的层次。有些方法是对系统外的,有些方法是系统内部使用的。把需要暴露给外部的功能集中到外观中,这样既方便客户端使用,也很好地隐藏了内部的细节
缺点:
- 如果外观过多的话,将不知道是调用模块还是外观
- 在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”,如果想要解决这个问题可以改用抽象外观类,当有新的业务需求要添加的时候,可以对应增加一个新的具体外观类
?什么时候用外观:
- 如果希望为一个复杂的子系统提供一个简单接口的时候,可以考虑使用外观模式。使用外观对象来实现大部分客户需要的功能,从而简化客户的使用
- 如果想要让客户程序和抽象类的实现部分松散耦合,可以考虑使用外观模式,使用外观对象来将这个子系统与它的客户分离开来,从而提高子系统的独立性和可移植性。
- 如果构建多层结构的系统,可以考虑使用外观模式,使用外观对象作为每层的入口,这样可以简化层间调用,也可以松散层次之间的依赖关系。