👉文章示例代码👈

定义

为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

外观模式又叫门面模式,是符合迪米特法则的。

场景示例

作为一个病人去医院看病,可能需要经过挂号、门诊、划价、取药这几个步骤。

相对来说过程可能较为繁琐,先来看下代码层面是如何实现的。

创建实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* @author zhh
* @description 病人类
* @date 2020-02-13 16:21
*/
public class Patient {

/**
* 姓名
*/
private String name;

public Patient(String name) {
this.name = name;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

创建子系统类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
* @author zhh
* @description 挂号类
* @date 2020-02-13 16:15
*/
public class RegisterService {

public void operate(Patient patient) {
// 模拟挂号
System.out.println("病人: " + patient.getName() + " 进行挂号");
System.out.println("挂号完毕");
}
}

/**
* @author zhh
* @description 门诊类
* @date 2020-02-13 16:18
*/
public class OutpatientService {

public void operate(Patient patient) {
// 模拟就诊
System.out.println("病人: " + patient.getName() + " 进行就诊");
System.out.println("就诊完毕, 需要配药");
}
}

/**
* @author zhh
* @description 划价类
* @date 2020-02-13 16:19
*/
public class SettleAccountService {

public void operate(Patient patient) {
// 模拟付款
System.out.println("病人: " + patient.getName() + " 进行付款");
System.out.println("付款完成, 前往取药");
}
}

/**
* @author zhh
* @description 取药类
* @date 2020-02-13 16:23
*/
public class TakeMedicineService {

public void operate(Patient patient) {
// 模拟取药
System.out.println("病人: " + patient.getName() + " 进行取药");
}
}

测试类及输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* @author zhh
* @description 测试类
* @date 2020-02-13 16:24
*/
public class Test {

public static void main(String[] args) {
Patient patient = new Patient("海豪");
System.out.println("病人进入医院");
new RegisterService().operate(patient);
new OutpatientService().operate(patient);
new SettleAccountService().operate(patient);
new TakeMedicineService().operate(patient);
System.out.println("病人离开医院");
}
}

测试类输出的结果如下:

病人进入医院
病人: 海豪 进行挂号
挂号完毕
病人: 海豪 进行就诊
就诊完毕, 需要配药
病人: 海豪 进行付款
付款完成, 前往取药
病人: 海豪 进行取药
病人离开医院

类结构图

以上示例类的结构图如下所示
image.png

从类结构图中我们可以发现,客户端对于挂号、门诊、划价、取药这些子系统的依赖性过高。各自子系统代码的变动很可能引起客户端代码变动,从而造成风险。

方案改进

我们可以在上述示例中通过建立客户端与子系统间的外观类来松散耦合。

实体类与子系统类我们不做改动。

创建接待员类

这里的接待员实际上就是外观模式中的外观类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* @author zhh
* @description 接待员类
* @date 2020-02-13 16:46
*/
public class Receptionist {

// 模拟注入
private RegisterService registerService = new RegisterService();
private OutpatientService outpatientService = new OutpatientService();
private SettleAccountService settleAccountService = new SettleAccountService();
private TakeMedicineService takeMedicineService = new TakeMedicineService();


/**
* 接待病人
* @param patient
*/
public void receivePatients(Patient patient) {
System.out.println("接待员开始接待病人");
registerService.operate(patient);
outpatientService.operate(patient);
settleAccountService.operate(patient);
takeMedicineService.operate(patient);
}
}

测试类及输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* @author zhh
* @description 测试类
* @date 2020-02-13 16:24
*/
public class Test {

public static void main(String[] args) {
Patient patient = new Patient("海豪");
Receptionist receptionist = new Receptionist();

System.out.println("病人进入医院");
receptionist.receivePatients(patient);
System.out.println("病人离开医院");
}
}

测试类输出的结果如下:

病人进入医院
接待员开始接待病人
病人: 海豪 进行挂号
挂号完毕
病人: 海豪 进行就诊
就诊完毕, 需要配药
病人: 海豪 进行付款
付款完成, 前往取药
病人: 海豪 进行取药
病人离开医院

类结构图

以上示例类的结构图如下所示
image.png

我们可以从类图中看出,通过外观模式改动后,客户端与子系统不再有直接的联系,客户端只与外观类进行交互,子系统也只与外观类进行交互。

总结

适用场景

  • 子系统越来越复杂,增加外观模式提供简单调用接口
  • 构建多层系统结构,利用外观对象作为每层的入口,简化层与层之间的调用

优点

  • 松散耦合,减少系统间依赖
  • 简化调用过程操作,提高灵活性
  • 更好地划分访问层次

缺点

新增或者扩展子系统行为不符合开闭原则,容易引入风险

参考