Java设计模式总结:Facade Pattern(外观模式)

概述

概念

外观模式(Facade Pattern)隐藏了系统的复杂性,并向客户端提供了一个可以访问系统的接口。这种类型的设计模式属于结构性模式。为子系统中的一组接口提供了一个能够统一访问的高层接口,这个接口使得子系统更容易被访问或者使用。

主要组成部分

  1. 门面:外观模式的核心。客户端调用门面角色,它熟悉子系统的功能。门面内部根据客户端需求封装了子系统功能的组合。
  2. 子系统:实现子系统的功能。对客户端和门面都隐藏了其内部细节。子系统内部可以有系统内的相互交互,也可以提供外界调用的接口。
  3. 客户端:通过调用门面来完成要实现的功能。

为什么要用外观模式

优点

  1. 松耦合:解除客户端子系统之间的耦合,让子系统内部的模块功能更易于扩展和维护。
  2. 易用性:客户端不必知道子系统复杂的内部实现方式,只需跟门面交互即可。
  3. 内外分离:子系统中,有些方法是对系统外的,有些方法是对系统内部相互交互使用的。子系统把暴露给外部的功能集中到门面中,从而实现客户端在不知道子系统内部细节前提下的调用

使用场景

  1. 为复杂的模块或子系统提供外界访问的模块
  2. 子系统相互独立
  3. 在层析结构中,可以使用外观模式定义系统的每一层的入口

快速入门

举一个应用外观模式的简单栗子。
有一个家庭影院,有 DVD播放器-DVDPlayer,爆米花机-Popcorn,投影仪-Projector,屏幕-Screen,立体声-Stereo,影院灯光-TheaterLight 等设备。当家庭影院执行准备、播放、暂停、结束等方法时,会调用不同的子系统中的不同方法。使用外观模式后,可以让客户端和子系统之间解耦。
HomeTheaterFacade

实例结构

1565608447416

子系统(ChildSystem)

  1. DVDPlayer

    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
    package com.wzc.childSystem;

    public class DVDPlayer {

    // 使用单例模式,使用饿汉式
    private static DVDPlayer instanse = new DVDPlayer();

    public static DVDPlayer getInstance() {
    return instanse;
    }

    public void on() {
    System.out.println(" dvd on ");
    }

    public void off() {
    System.out.println(" dvd off ");
    }

    public void play() {
    System.out.println(" dvd is playing... ");
    }

    public void pause() {
    System.out.println(" dvd pause... ");
    }
    }
  2. Popcorn

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package com.wzc.childSystem;

    public class Popcorn {

    // 饿汉式
    private static Popcorn instance = new Popcorn();

    public static Popcorn getInstance(){
    return instance;
    }

    public void on() {
    System.out.println(" popcorn on ");
    }

    public void off() {
    System.out.println(" popcorn off ");
    }

    public void pop() {
    System.out.println(" popcorn is poping... ");
    }
    }
  3. Projector

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    package com.wzc.childSystem;

    public class Projector {

    private static Projector instance = new Projector();

    public static Projector getInstance(){
    return instance;
    }

    public void on(){
    System.out.println(" projector on ");
    }

    public void off(){
    System.out.println(" projector off ");
    }

    public void focus(){
    System.out.println(" projector is focus... ");
    }
    }
  1. Screen

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package com.wzc.childSystem;

    public class Screen {

    private static Screen instance = new Screen();

    public static Screen getInstance(){
    return instance;
    }

    public void up(){
    System.out.println(" Screen up ");
    }

    public void down(){
    System.out.println(" Screen down ");
    }
    }
  1. Stereo

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    package com.wzc.childSystem;

    public class Stereo {

    private static Stereo instance = new Stereo();

    public static Stereo getInstance(){
    return instance;
    }

    public void on(){
    System.out.println(" Stereo on ");
    }

    public void off(){
    System.out.println(" Stereo off ");
    }

    public void up(){
    System.out.println(" Stereo up ");
    }
    }
  1. TheaterLight

    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
    package com.wzc.childSystem;

    public class TheaterLight {

    private static TheaterLight instance = new TheaterLight();

    public static TheaterLight getInstance(){
    return instance;
    }

    public void on(){
    System.out.println(" TheaterLight on ");
    }

    public void off(){
    System.out.println(" TheaterLight off ");
    }

    public void dim(){
    System.out.println(" TheaterLight dim ");
    }

    public void bright(){
    System.out.println(" TheaterLight bright ");
    }
    }

门面(Facade)

门面类中包含4个方法,对子系统中的细节进行了封装

  1. 当需要准备家庭影院看电影时,执行影院 Facade 的 ready() 方法,需要进行以下步骤:
    打开爆米花机 → 爆米花机工作 → 降下屏幕 → 打开投影仪 → 打开立体声 → 打开DVD播放器 → 调暗灯光
  2. 当使用家庭影院播放时,执行 play() 方法
  3. 当使用家庭影院暂停播放时,执行 pause() 方法
  4. 当需要结束看电影活动时,执行影院 Facade 的 end() 方法,需要进行以下步骤:
    关闭爆米花机 → 调亮灯光 → 升起屏幕 → 关闭投影仪 → 关闭立体声 → 关闭DVD播放器

代码如下

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
55
package com.wzc.facade;

import com.wzc.childSystem.*;

/**
* 家庭影院门面类 Facade
*/
public class HomeTheaterFacade {

// 定义各个子系统的对象
private TheaterLight theaterLight;
private Popcorn popcorn;
private Projector projector;
private Stereo stereo;
private Screen screen;
private DVDPlayer dvdPlayer;

// 构造器
public HomeTheaterFacade() {
this.theaterLight = TheaterLight.getInstance();
this.popcorn = Popcorn.getInstance();
this.projector = Projector.getInstance();
this.stereo = Stereo.getInstance();
this.screen = Screen.getInstance();
this.dvdPlayer = DVDPlayer.getInstance();
}

//操作分为4步
public void ready(){
popcorn.on();
popcorn.pop();
screen.down();
projector.on();
stereo.on();
dvdPlayer.on();
theaterLight.dim();
}

public void play(){
dvdPlayer.play();
}

public void pause(){
dvdPlayer.pause();
}

public void end(){
popcorn.off();
theaterLight.bright();
screen.up();
projector.off();
stereo.off();
dvdPlayer.off();
}
}

客户端(Client)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.wzc;

import com.wzc.facade.HomeTheaterFacade;

/**
* 家庭影院客户端 Client
*/
public class Client {
public static void main(String[] args) {
// 直接调用各个设备,很麻烦
// 外观类
HomeTheaterFacade homeTheaterFacade = new HomeTheaterFacade();
homeTheaterFacade.ready();
homeTheaterFacade.play();
homeTheaterFacade.pause();
homeTheaterFacade.end();
}
}

运行结果

1565610685367
直接调用各子系统中的方法,步骤非常繁琐,现在我们只需要调用 Facade 中的方法即可操作家庭影院,而不需要知道子系统内部的实现细节,甚至都不用知道子系统内部的构成。客户端只需要与 Facade 交互就可以了。

外观模式在 MyBatis 中的应用

在 Configuration 类中创建 MetaObject 对象时用到了外观模式。
1565667182397

可以看出,如果直接调用子系统的 forObject 方法,需要传入的形参过多,比较繁琐。Configuration 类使用外观模式,Facade 类只负责调用和管理子系统,其中 newMetaObject 方法实际上隐藏了子系统的细节。Configuration 类中定义了objectFactory, objectWrapperFactory, reflectorFactory 三个子系统
1565665063221

Configuration 的构造器
1565666929826

跟踪 MetaObject,查看 forObject 方法
1565667250819

MetaObject 的构造函数
1565665801917

其实,MetaObject 也是一个将构造器私有化单例模式应用(类似实例中的家庭影院),各类之间的关系如下

mybatisFacade

总结

外观模式的主要作用是通过门面类 Facade客户端子系统之间解耦,客户端不必知道子系统内部细节,只需要通过 Facade 暴露的方法就能实现对子系统功能组合的调用。

其实,生活中也有外观模式的实例,比如找装修公司装修房子。客户可以不需要知道装修选材、人员、实施方案等内部细节,只需要告诉装修公司自己需求的装修效果,装修公司就会实现房屋的装修。






扫一扫,关注我的微信公众号↓

0%