门面模式
门面模式(Facade Pattern)又叫外观模式,提供了一个统一的接口,用来访问子系统中的一群接口。主要特征为定义一个高层接口,让子系统更容易使用,属于结构性模式。
举个栗子
新生入学的例子
新生在入学时需要办理一系列手续,那么作为学生他就得知道办理手续的流程,例如首先要拿上身份证到教务处报道,再根据学号和姓名到班主任处报告,最后根据学号到宿舍处报道,然后完成一系列的报道流程以后才能正式入学。
那么在这个过程中,学生首先需要了解他报道的顺序,然后还需要了解具体的报道地点时。基于他对学校的不熟悉,所以完成报道的过程可能不是一件容易的事。
这个时候就可以考虑设立“新生接待员”,只要把新生交托给接待员,然后由接待员带领学生去进行报道流程,接待员了解和熟悉整个报道流程以及各个地点。
在这个过程中,学生要做的唯一事情就是找到接待员,剩余的事情都交由接待员来处理,对于学生来说是很方便的,他不必去关心具体的报道流程,也不必关系具体的报道地点。
实现demo
public class Student {
/**
* 学号
*/
private Integer id;
/**
* 姓名
*/
private String name;
/**
* 身份证
*/
private String card;
}
/**
* @author liuwenxiu
* @Description: 教务处处理入学手续相关流程
* @date 2022-7-8 10:25
*/
public interface AcademicAffairsOfficeService {
/**
* 根据身份证号办理入学手续
* @param card 身份证号
* @return 学号
* @throws Exception 当前身份证号没有对应学生存在
*/
Integer enrollmentByCard(String card) throws Exception;
}
@Service
public class AcademicAffairsOfficeServiceImpl implements AcademicAffairsOfficeService {
@Override
public Integer enrollmentByCard(String card) throws Exception {
// 根据身份证号查询该学生的学号以及班级
Integer id = null;
switch (card) {
case "1" :
id = 1001;
break;
case "2":
id = 1002;
break;
// ... 省略部分case
default:
throw new Exception("很抱歉,您不是本校学生");
}
System.out.println("【教务处】:身份证为" + card + "的同学的手续办理成功,该学生学号为:" + id.toString());
return id;
}
}
public interface TeacherService {
/**
* 根据学号和姓名到班主任处处理入学手续
* @param id 学号
* @param name 姓名
* @return
*/
void checkInByTeacher(Integer id, String name);
}
/**
* @author liuwenxiu
* @Description: 班主任处理入学手续相关流程
* @date 2022-7-8 10:31
*/
@Service
public class TeacherServiceImpl implements TeacherService {
@Override
public void checkInByTeacher(Integer id, String name) {
System.out.println("【班主任】:学号为" + id.toString() + "且姓名为" + name + "的同学手续办理成功");
}
}
/**
* @author liuwenxiu
* @Description: 宿舍处入学相关手续
* @date 2022-7-8 10:28
*/
public interface DormitoryService {
/**
* 根据学号到宿舍处办理入学手续
* @param id 学号
*/
void checkInByDormitory(Integer id);
}
@Service
public class DormitoryServiceImpl implements DormitoryService {
@Override
public void checkInByDormitory(Integer id) {
System.out.println("【宿舍处】:学号为" + id.toString() + "的同学手续办理成功");
}
}
基础例子
@Test
public void testStudent() {
Student student = new Student();
student.setCard("2");
student.setName("林小明");
// 假设这是学生办理入学业务的流程
try {
Integer stuId = academicAffairsOfficeService.enrollmentByCard(student.getCard());
student.setId(stuId);
teacherService.checkInByTeacher(student.getId(),student.getName());
dormitoryService.checkInByDormitory(student.getId());
} catch (Exception e) {
e.printStackTrace();
}
}
【教务处】:身份证为2的同学的手续办理成功,该学生学号为:1002
【班主任】:学号为1002且姓名为林小明的同学手续办理成功
【宿舍处】:学号为1002的同学手续办理成功
门面模式例子
public interface FacadeService {
/**
* 办理入学手续
* @param student
*/
void checkIn(Student student) throws Exception;
}
@Override
public void checkIn(Student student) throws Exception {
Integer stuId = academicAffairsOfficeService.enrollmentByCard(student.getCard());
student.setId(stuId);
teacherService.checkInByTeacher(student.getId(),student.getName());
dormitoryService.checkInByDormitory(student.getId());
}
@Test
public void testFacade(){
Student student = new Student();
student.setCard("2");
student.setName("林小明");
try {
facadeService.checkIn(student);
} catch (Exception e) {
e.printStackTrace();
}
}
【教务处】:身份证为2的同学的手续办理成功,该学生学号为:1002
【班主任】:学号为1002且姓名为林小明的同学手续办理成功
【宿舍处】:学号为1002的同学手续办理成功
总结
- 对于用户来说使用门面模式会简单便捷
在正常情况下学生入学,需要知晓调用三个Service的顺序以及需要传递的参数,如demo1所示。而在使用门面模式的情况下,代码中只依赖一个Facade,只需要通过调用Facade的方法就可以完成入学的操作。 - 解耦合
解耦合了客户端和子系统。客户端调用不需要了解门面的内部实现逻辑,如果入学流程变化,只需要修改FacadeService的方法,客户端的代码不需要进行改动。 - 维护门面Facade会变得复杂
门面的实现类FacadeServiceImpl需要注入多个类,会导致其可维护性变差门面模式结构
如图所示,假设子系统中有三个模块,分别是ModuleA、ModuleB和ModuleC,他们分别有一个方法。图上一共出现了两种角色:
1、门面角色(Facade):客户端可以调用这个角色的方法。这个角色知晓相关的(一个或多个)子系统的功能和责任,在正常的情况下,本角色会将所有从客户端发来的请求委派到相应的子系统去。
2、子系统角色(Subsytem):可以同时有一个或多个子系统,每个子系统都不是一个单独的类,而是一个类的集合。每个子系统都可以被客户端直接调用或者被门面角色调用,子系统并不知道门面的存在,对于子系统而言,门面仅仅是另外一个客户端而已。
常见例子
日志处理,市面上日志框架很多例如Log4j、Log4j2、Logback等,每套都有自己的Api。但是在开发时我们依赖于slf4j,如果我们切换了日志框架,对于业务代码不会产生影响。