外观模式

外观模式

外观模式又称为门面模式是一种比较简单但却十分常用的设计模式,它的定义是:

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

外观模式通过提供一个外观类,来简化客户端与子系统的交互,为复杂的子系统提供一个统一的入口。

简单概述

  举一个简单的例子,一日三餐是我们每个人的必须,而怎么吃我们可以有两种不同的选择,一是自己买菜烹饪,二是直接去饭店吃饭。这两种的区别相信大家都有感触,第一种我们自己烹饪,首先我们要去市场买菜,买调味料,甚至是锅碗瓢盆(单身狗的悲哀-_-||),然后再回家点火炒菜,如果一切顺利的话,你才有可能吃上一顿,填饱肚子。但是选择第二种就简单很多了,我们只要去饭店直接点菜,然后稍等一会就能吃上一顿美餐。在这里,买菜、买调味料、烹饪等都是子系统的一个个方法,而饭店就是外观类,选择第一种意味着作为客户端的我们需要与子系统进行复杂的交互,而第二种客户端仅仅与外观类进行一次交互即能得到同样的期望。如下图:

facade_food

  在软件开发中,当我们需要完成一个较为复杂的功能时,往往需要与多个子系统进行交互,当子系统比较简单时,我们尚且能正常的维护使用,但是当子系统过于复杂时,会导致代码逻辑混乱,不仅是对开发人员不方便,也会使得系统维护的难度大大提高。这种时候我们需要一个外观类来将复杂的业务逻辑进行封装,只提供一个简单的public方法供客户端调用。对于客户端来说,他并不需要知道复杂的业务逻辑,只需提供必要的参数,就能得到想要的结果。即便业务逻辑发生改动,我们也仅需修改外观类中与子系统交互的逻辑,而不影响客户端代码。

图示

外观模式没有一个统一的UML图,更多的时候使用如下示意图来表示外观模式。

facade_food

代码示例

我们先创建几个子系统的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class SubSystem01 {

public void method01(){
System.out.println("子系统01的方法01");
}
}

public class SubSystem02 {

public void method02(){
System.out.println("子系统02的方法02");
}
}

public class SubSystem03 {

public void method03(){
System.out.println("子系统03的方法03");
}
}

每个子系统各自都含有一个自己的方法,当客户端需要依次使用这些方法时,我们需要依次创建子系统的实例再调用它。

1
2
3
4
5
6
7
8
9
10
11
12
public class Client {

public static void main(String[] args){
SubSystem01 subSystem01 = new SubSystem01();
SubSystem02 subSystem02 = new SubSystem02();
SubSystem03 subSystem03 = new SubSystem03();

subSystem01.method01();
subSystem02.method02();
subSystem03.method03();
}
}

这种情况下,我们需要牢记各个子系统方法调用的时机以及结果,任何一步错了都将导致系统的崩溃。此时如果我们创建一个外观类,并将所有的逻辑操作都交于外观类处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class FacadeClass {
private SubSystem01 system01;
private SubSystem02 system02;
private SubSystem03 system03;

public FacadeClass() {
system01 = new SubSystem01();
system02 = new SubSystem02();
system03 = new SubSystem03();
}

public void method(){
system01.method01();
system02.method02();
system03.method03();
}
}

此时客户端所需做的仅需创建一个外观类,并调用它提供的一个public方法即可

1
2
3
4
5
6
7
public class Client {

public static void main(String[] args){
FacadeClass facadeClass = new FacadeClass();
facadeClass.method();
}
}

通过使用外观模式,我们实现了程序的高内聚,低耦合,将与子系统的交互都交于外观类,减少了客户端与子系统的耦合,这样也就提高了系统的灵活性,不管子系统如何变化,只要外观类没有变化,都不会影响程序的运行。

抽象外观模式

  上面所说的外观模式其实有一个问题,它不符合开闭原则,当子系统的调用逻辑发生改变,例如我们要在SubSystem02.method02()SubSystem03.method03()之间增加一个新的子系统方法SubSystem04.method04(),此时我们就需要去修改FacadeClass 类的方法,这不符合对拓展开放,对修改关闭的原则。
  此时我们可以通过增加一个抽象外观类来增加系统的灵活性,面向抽象开发也更符合依赖倒置的原则,当我们需要修改外观类的逻辑时,我们可以通过增加一个外观类来解决,客户端在使用时再决定使用哪一个外观类。

抽象外观类

1
2
3
4
5
6
7
8
9
10
11
12
13
public abstract class AbstractFacade{
protected SubSystem01 system01;
protected SubSystem02 system02;
protected SubSystem03 system03;

public AbstractFacade() {
system01 = new SubSystem01();
system02 = new SubSystem02();
system03 = new SubSystem03();
}

public abstract void method();
}

这里可以看到,我在抽象外观类里初始化了一些子系统,它的继承类也就可以直接使用这些子系统
两个外观类

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
public class FacadeClass01 extends AbstractFacade {

public FacadeClass01() {
super();
}

@Override
public void method() {
system01.method01();
system02.method02();
system03.method03();
}
}

public class FacadeClass02 extends AbstractFacade {
SubSystem04 system04;

public FacadeClass02() {
super();
system04 = new SubSystem04();
}

@Override
public void method() {
system01.method01();
system02.method02();
system04.method04();
system03.method03();
}
}

客户端使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Client {

public static void main(String[] args){
AbstractFacade facade;

facade = new FacadeClass01();
facade.method();

System.out.println("---------------------------------");

facade = new FacadeClass02();
facade.method();
}
}

系统运行结果

1
2
3
4
5
6
7
8
子系统01的方法01
子系统02的方法02
子系统03的方法03
---------------------------------
子系统01的方法01
子系统02的方法02
子系统04的method04方法
子系统03的方法03

外观模式的优缺点

优点

  • 减少了客户端需要连接的子系统个数,简化客户端操作的复杂性
  • 实现低耦合,高内聚
  • 提高了安全性,通过外观类限制客户端对子系统随意的访问,只能调用特定的子系统来完成操作

缺点

  • 限制了客户端对子系统的访问,也就意味着降低了系统的灵活性
  • 如果系统设计不当,可能破坏设计模式的开闭原则,子系统改变而需修改外观类的源码

外观模式是我们实际开发中十分常用的设计模式,例如工具类的封装,我们常常会将一些比较常用的处理逻辑封装成一个工具类,客户端直接调用工具类提供的公共方法,而不需考虑方法的内部实现,使开发逻辑更为清晰。在实际使用中,我们还是要根据实际情况来选择合适的设计模式。

坚持原创技术分享,您的支持将鼓励我继续创作!