门面模式
# 门面模式
# 定义
为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
在门面模式中,存在一个称为门面的类,它封装了一组子系统的功能。门面类充当了客户端和子系统之间的中间层,简化了客户端与子系统之间的交互。客户端只需要与门面类进行交互,而不需要直接与子系统的组件进行通信。
# 结构
门面(Facade):门面是门面模式的核心角色,它提供了一个简化的接口,用于访问子系统的功能。门面封装了子系统的复杂性,将客户端的请求转发给适当的子系统组件。
子系统(Subsystems):子系统是实际执行功能的组件集合。它们包含了复杂的逻辑和操作,但对于客户端来说是不可见的。门面通过与子系统进行交互来完成客户端的请求。
- img: https://bitouyun.com/images/design-pattern/facade.png
link: https://bitouyun.com/images/design-pattern/facade.png
name: 门面模式
- img: https://bitouyun.com/images/design-pattern/facade2.png
link: https://bitouyun.com/images/design-pattern/facade2.png
name: 门面模式
2
3
4
5
6
# 优点
简化客户端代码:门面模式提供了一个简化的接口,隐藏了子系统的复杂性,从而简化了客户端的代码。客户端只需要与门面进行交互,而不需要了解子系统的内部实现细节。
解耦客户端和子系统:门面模式将客户端与子系统之间进行了解耦。客户端只与门面进行交互,对于子系统的变化是透明的。这样可以提高系统的灵活性和可维护性。
提供高层接口:门面类可以根据客户端的需求,组合和调用子系统的功能,从而提供更高层次的接口。这样可以简化客户端的操作,并且可以更好地组织和管理子系统的功能。
隐藏复杂性:门面模式通过将复杂性封装在门面内部,使得客户端只需要关注简化的接口。这可以降低开发过程中的认知负担,并提高代码的可读性。
# 缺点
违反开闭原则:当需要添加新的功能或修改现有功能时,可能需要修改门面类。这可能会导致门面类的变得庞大,并且需要修改客户端代码。
增加了系统的复杂性:引入门面模式会增加一个额外的抽象层,这可能会增加系统的复杂性。过度使用门面模式可能导致系统变得过于复杂和难以理解。
灵活和高效性:门面模式适用于需要隐藏复杂子系统的情况,但并不适用于所有场景。在某些情况下,直接与子系统进行交互可能更加灵活和高效。
# 应用场景
简化复杂子系统:当存在一个复杂的子系统,其中包含多个组件或服务,并且客户端需要与这些组件进行交互时,门面模式可以提供一个简化的接口,将子系统的复杂性隐藏起来。这样可以降低客户端代码的复杂性,并提供一个更易于使用的接口。
提供统一的接口:当需要将多个相关的子系统组合起来,为客户端提供一个统一的接口时,可以使用门面模式。门面类可以根据客户端的需求,调用适当的子系统组件,并组合其功能。这样可以简化客户端的操作,并提供更高层次的接口。
封装遗留系统:当存在一个遗留系统,其中的代码结构较为混乱且难以理解时,可以引入门面模式来封装该系统。门面模式可以提供一个清晰的接口层,隐藏底层系统的复杂性和混乱的代码结构。这样可以帮助团队更好地组织和维护遗留系统。
松耦合子系统和客户端:门面模式可以将客户端代码与子系统解耦,使得客户端不需要直接依赖于子系统的具体实现。这样可以提高系统的灵活性和可维护性,使得子系统的变化对客户端来说是透明的。
# 解决的问题
解决易用性问题:门面模式可以用来封装系统的底层实现,隐藏系统的复杂性,提供一组更加简单易用、更高层的接口。
解决性能问题: 前端和服务器之间是通过网络通信的,网络通信耗时比较多,为了提高响应速度,我们要尽量减少前端与服务器之间的网络通信次数。我们可以将多个接口的数放入一个门面接口中。
解决分布式事务问题:将多个接口的调用放入一个门面接口中,并使用事务来控制。
# 示例代码1
场景1
文件系统的访问会涉及多个底层操作,如打开文件、读取、写入、关闭等。通过门面模式封装这些底层操作的复杂性,提供一个简单的接口供客户端进行文件操作。
/**
* 文件子系统
*/
@Slf4j
public class FileSystem {
public void openFile(String fileName) {
log.info("打开文件:{}", fileName);
}
public void readFile(String fileName) {
log.info("读取文件:{}", fileName);
}
public void writeFile(String fileName) {
log.info("写入文件:{}", fileName);
}
public void closeFile(String fileName) {
log.info("关闭文件:{}", fileName);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 文件系统门面
* 封装文件系统访问的复杂性
*/
public class FileSystemFacade {
private FileSystem fileSystem;
public FileSystemFacade(FileSystem fileSystem) {
this.fileSystem = fileSystem;
}
// 读取文件
public void readFile(String fileName) {
fileSystem.openFile(fileName);
fileSystem.readFile(fileName);
fileSystem.closeFile(fileName);
}
// 写入文件
public void writeFile(String fileName) {
fileSystem.openFile(fileName);
fileSystem.writeFile(fileName);
fileSystem.closeFile(fileName);
}
}
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
/**
* 测试类
* 通过门面类FileSystemFacade提供的简化接口进行读取和写入操作
*/
public class Client {
public static void main(String[] args) {
FileSystem fileSystem = new FileSystem();
FileSystemFacade facade = new FileSystemFacade(fileSystem);
facade.readFile("读取门面模式.txt");
facade.writeFile("写入门面模式.txt");
}
}
2
3
4
5
6
7
8
9
10
11
12
// Make sure to add code blocks to your code group
# 示例代码2
场景2
客户端通过门面模式调用不同子系统的业务
/**
* 门面类
* 提供客户端访问接口
*/
public class Facade {
// 业务系统
private SystemA systemA;
private SystemB systemB;
private SystemC systemC;
public Facade(SystemA systemA, SystemB systemB, SystemC systemC) {
this.systemA = systemA;
this.systemB = systemB;
this.systemC = systemC;
}
// 业务A
public void doSomethingA() {
systemA.doSomethingA();
}
// 业务B
public void doSomethingB() {
systemB.doSomethingB();
}
// 业务C
public void doSomethingC() {
systemC.doSomethingC();
}
}
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
/**
* 子系统A
*/
@Slf4j
public class SystemA {
public void doSomethingA() {
log.info("子系统A执行业务逻辑A");
}
}
/**
* 子系统B
*/
@Slf4j
public class SystemB {
public void doSomethingB() {
log.info("子系统B执行业务逻辑B");
}
}
/**
* 子系统A
*/
@Slf4j
public class SystemC {
public void doSomethingC() {
log.info("子系统C执行业务逻辑C");
}
}
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
/**
* 测试类
*/
public class Client {
public static void main(String[] args) {
// 子系统对象
SystemA systemA = new SystemA();
SystemB systemB = new SystemB();
SystemC systemC = new SystemC();
// 门面对象
Facade facade = new Facade(systemA, systemB, systemC);
facade.doSomethingA();
facade.doSomethingB();
facade.doSomethingC();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Make sure to add code blocks to your code group
# 常见问题
一个子系统可以有多少个门面?
一般情况下,一个子系统有一个门面就足够,如果门面类庞大到不能忍受的程度可以拆分成多个门面;
调用子系统的客户端有不同的权限,不能共用统一的门面时,可以新建一个门面,在新建的门面中引用原有的门面,对外提供统一的子系统方法。
门面接口能参与子系统的业务逻辑吗?
门面接口中不能参与子系统的业务逻辑,如果参与业务逻辑会引发一个倒依赖的问题,对外提供的业务必须依赖门面接口,让子系统依赖门面接口才能实现某个功能,是一个设计上的错误,破坏了系统的封装性。可以通过新建一个类或方法封装完后提供给门面类。