门面模式

门面模式(Facade Pattern)又叫外观模式,提供了一个统一的接口,用来访问子系统中的一群接口。主要特征为定义一个高层接口,让子系统更容易使用,属于结构性模式。

举个栗子

新生入学的例子
新生在入学时需要办理一系列手续,那么作为学生他就得知道办理手续的流程,例如首先要拿上身份证到教务处报道,再根据学号和姓名到班主任处报告,最后根据学号到宿舍处报道,然后完成一系列的报道流程以后才能正式入学。
那么在这个过程中,学生首先需要了解他报道的顺序,然后还需要了解具体的报道地点时。基于他对学校的不熟悉,所以完成报道的过程可能不是一件容易的事。
这个时候就可以考虑设立“新生接待员”,只要把新生交托给接待员,然后由接待员带领学生去进行报道流程,接待员了解和熟悉整个报道流程以及各个地点。
在这个过程中,学生要做的唯一事情就是找到接待员剩余的事情都交由接待员来处理,对于学生来说是很方便的,他不必去关心具体的报道流程,也不必关系具体的报道地点。

实现demo

  1. public class Student {
  2. /**
  3. * 学号
  4. */
  5. private Integer id;
  6. /**
  7. * 姓名
  8. */
  9. private String name;
  10. /**
  11. * 身份证
  12. */
  13. private String card;
  14. }
  1. /**
  2. * @author liuwenxiu
  3. * @Description: 教务处处理入学手续相关流程
  4. * @date 2022-7-8 10:25
  5. */
  6. public interface AcademicAffairsOfficeService {
  7. /**
  8. * 根据身份证号办理入学手续
  9. * @param card 身份证号
  10. * @return 学号
  11. * @throws Exception 当前身份证号没有对应学生存在
  12. */
  13. Integer enrollmentByCard(String card) throws Exception;
  14. }
  15. @Service
  16. public class AcademicAffairsOfficeServiceImpl implements AcademicAffairsOfficeService {
  17. @Override
  18. public Integer enrollmentByCard(String card) throws Exception {
  19. // 根据身份证号查询该学生的学号以及班级
  20. Integer id = null;
  21. switch (card) {
  22. case "1" :
  23. id = 1001;
  24. break;
  25. case "2":
  26. id = 1002;
  27. break;
  28. // ... 省略部分case
  29. default:
  30. throw new Exception("很抱歉,您不是本校学生");
  31. }
  32. System.out.println("【教务处】:身份证为" + card + "的同学的手续办理成功,该学生学号为:" + id.toString());
  33. return id;
  34. }
  35. }
  36. public interface TeacherService {
  37. /**
  38. * 根据学号和姓名到班主任处处理入学手续
  39. * @param id 学号
  40. * @param name 姓名
  41. * @return
  42. */
  43. void checkInByTeacher(Integer id, String name);
  44. }
  45. /**
  46. * @author liuwenxiu
  47. * @Description: 班主任处理入学手续相关流程
  48. * @date 2022-7-8 10:31
  49. */
  50. @Service
  51. public class TeacherServiceImpl implements TeacherService {
  52. @Override
  53. public void checkInByTeacher(Integer id, String name) {
  54. System.out.println("【班主任】:学号为" + id.toString() + "且姓名为" + name + "的同学手续办理成功");
  55. }
  56. }
  57. /**
  58. * @author liuwenxiu
  59. * @Description: 宿舍处入学相关手续
  60. * @date 2022-7-8 10:28
  61. */
  62. public interface DormitoryService {
  63. /**
  64. * 根据学号到宿舍处办理入学手续
  65. * @param id 学号
  66. */
  67. void checkInByDormitory(Integer id);
  68. }
  69. @Service
  70. public class DormitoryServiceImpl implements DormitoryService {
  71. @Override
  72. public void checkInByDormitory(Integer id) {
  73. System.out.println("【宿舍处】:学号为" + id.toString() + "的同学手续办理成功");
  74. }
  75. }

基础例子

  1. @Test
  2. public void testStudent() {
  3. Student student = new Student();
  4. student.setCard("2");
  5. student.setName("林小明");
  6. // 假设这是学生办理入学业务的流程
  7. try {
  8. Integer stuId = academicAffairsOfficeService.enrollmentByCard(student.getCard());
  9. student.setId(stuId);
  10. teacherService.checkInByTeacher(student.getId(),student.getName());
  11. dormitoryService.checkInByDormitory(student.getId());
  12. } catch (Exception e) {
  13. e.printStackTrace();
  14. }
  15. }
  1. 【教务处】:身份证为2的同学的手续办理成功,该学生学号为:1002
  2. 【班主任】:学号为1002且姓名为林小明的同学手续办理成功
  3. 【宿舍处】:学号为1002的同学手续办理成功

门面模式例子

  1. public interface FacadeService {
  2. /**
  3. * 办理入学手续
  4. * @param student
  5. */
  6. void checkIn(Student student) throws Exception;
  7. }
  8. @Override
  9. public void checkIn(Student student) throws Exception {
  10. Integer stuId = academicAffairsOfficeService.enrollmentByCard(student.getCard());
  11. student.setId(stuId);
  12. teacherService.checkInByTeacher(student.getId(),student.getName());
  13. dormitoryService.checkInByDormitory(student.getId());
  14. }
  1. @Test
  2. public void testFacade(){
  3. Student student = new Student();
  4. student.setCard("2");
  5. student.setName("林小明");
  6. try {
  7. facadeService.checkIn(student);
  8. } catch (Exception e) {
  9. e.printStackTrace();
  10. }
  11. }
  1. 【教务处】:身份证为2的同学的手续办理成功,该学生学号为:1002
  2. 【班主任】:学号为1002且姓名为林小明的同学手续办理成功
  3. 【宿舍处】:学号为1002的同学手续办理成功

总结

  1. 对于用户来说使用门面模式会简单便捷
    在正常情况下学生入学,需要知晓调用三个Service的顺序以及需要传递的参数,如demo1所示。而在使用门面模式的情况下,代码中只依赖一个Facade,只需要通过调用Facade的方法就可以完成入学的操作。
  2. 解耦合
    解耦合了客户端和子系统。客户端调用不需要了解门面的内部实现逻辑,如果入学流程变化,只需要修改FacadeService的方法,客户端的代码不需要进行改动。
  3. 维护门面Facade会变得复杂
    门面的实现类FacadeServiceImpl需要注入多个类,会导致其可维护性变差

    门面模式结构

    设计模式 - 图1

如图所示,假设子系统中有三个模块,分别是ModuleA、ModuleB和ModuleC,他们分别有一个方法。图上一共出现了两种角色:
1、门面角色(Facade):客户端可以调用这个角色的方法。这个角色知晓相关的(一个或多个)子系统的功能和责任,在正常的情况下,本角色会将所有从客户端发来的请求委派到相应的子系统去
2、子系统角色(Subsytem):可以同时有一个或多个子系统,每个子系统都不是一个单独的类,而是一个类的集合。每个子系统都可以被客户端直接调用或者被门面角色调用,子系统并不知道门面的存在,对于子系统而言,门面仅仅是另外一个客户端而已。

常见例子

日志处理,市面上日志框架很多例如Log4j、Log4j2、Logback等,每套都有自己的Api。但是在开发时我们依赖于slf4j,如果我们切换了日志框架,对于业务代码不会产生影响。