外观 Facade

外观模式是一种结构型设计模式,能为程序库、框架或其他复杂类提供一个简单的接口或为子系统中的一组接口提供统一的接口。它定义了一个更高级别的接口,使子系统更易于使用。

外观模式的本质是简化调用,统一操作。


为什么要使用?

外观模式的对象职责:

为一些类提供一个简单或统一的接口。

例如,使用 Slf4j 日志🗒️框架统一 log4jlog4j2CommonLog 等日志框架。Slf4j 作为一个日志门面让我们无需关注日志实现就能实现日志功能。即使未来会出现更多的日志实现,只要依赖日志门面开发,那么就不需要额外的学习成本去学习新的日志框架。而且,就算后期系统需要更换日志实现,也不需要修改代码。只需要变更日志门面依赖的日志实现即可。

外观模式能让复杂的东西看起来简单,当我们在向其他人说使用这个类需要先调用那个类,还要在那个类注册一下时,我们就需要使用外观模式来统一并简化接口了。除此之外,在解决遗留系统重构的问题或分层架构中的扩展问题等也能使用外观模式。


模式结构

  • 外观(Facade)提供了一种访问特定子系统功能的便捷方式,其了解如何重定向客户端请求,知晓如何操作一切活动部件。
  • 创建附加外观(Additional Facade)类可以避免多种不相关的功能污染单一外观,使其变成又一个复杂结构。客户端和其他外观都可使用附加外观。
  • 复杂子系统(Complex Subsystem)由数十个不同对象构成。如果要用这些对象完成有意义的工作,你必须深入了解子系统的实现细节,比如按照正确顺序初始化对象和为其提供正确格式的数据。子系统类不会意识到外观的存在, 它们在系统内运作并且相互之间可直接进行交互。
  • 客户端(Client)使用外观代替对子系统对象的直接调用。

外观模式的类图:

从类图中,我们可以看出外观模式并没有具体的实现。

外观模式本身并不是一个代码实现的模式,而是组合更多的其他模式来使用的一种通用解决方案。


模式实现

该示例使用外观模式来为 Markdown 文档提供一个简单的生成接口。

示例程序的类图

代码实现

外观
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package example;

/** 外观类 */
public class PageMaker {
private PageMaker() {}

/** 简化接口,生成欢迎页面 */
public static void makeWelcomePage() {
MarkdownWriter markdownWriter = new MarkdownWriter();
String bilibili = Database.getProperties("Bilibili");
String bing = Database.getProperties("Bing");
String hellovie = Database.getProperties("Hellovie");
String github = Database.getProperties("Github");
markdownWriter.printTitle("欢迎页面");
markdownWriter.printQuote("软件设计模式(Design pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。");
markdownWriter.printParagraph("上个世纪 90 年代,Eric Gamma、Richard Helm、Ralph Johnson、John Vlissides 等 4 人合作出版了《设计模式:可复用面向对象软件的基础》。而 “设计模式” 这个概念也是从中而来。这 4 人也被称为 the Gang of Four,简称 GoF。\n" + "\n" + "“设计模式” 共 23 种,是一种能够提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。它是解决某些特定问题的一般性概念,能够根据需求进行调整,用于解决代码中反复出现的设计问题。\n" + "\n" + "合理利用 “设计模式” 开发会给我们带来很多的便利。而如何合理的运用就在于学会 “找到变化,封装变化”。\n");
markdownWriter.printTitle("其他链接");
markdownWriter.printLink(hellovie, "博客主页");
markdownWriter.printLink(bing, "必应");
markdownWriter.printLink(github, "Github");
markdownWriter.printLink(bilibili, "哔哩哔哩");
}
}
复杂子系统
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 example;

import java.util.HashMap;
import java.util.Map;

/** 获取数据的子系统类 */
public class Database {
private Database() { }

/**
* 获取数据
* @param name key
* @return value
*/
public static String getProperties(String name) {
Map<String, String> map = new HashMap<>(4);
map.put("Bilibili", "https://www.bilibili.com");
map.put("Bing", "https://cn.bing.com");
map.put("Hellovie", "https://hellovie.github.io");
map.put("Github", "https://github.com");
if (map.containsKey(name)) {
return map.get(name);
}
return "";
}
}
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
package example;

/** 生成markdown的子系统类 */
public class MarkdownWriter {
/**
* 打印标题
* @param title 标题
*/
public void printTitle(String title) {
System.out.println("## " + title);
}

/**
* 打印链接
* @param link 链接
* @param name 名字
*/
public void printLink(String link, String name) {
System.out.println("* [" + name + "](" + link + ")");
}

/**
* 打印正文
* @param content 内容
*/
public void printParagraph(String content) {
System.out.println(content);
}

/**
* 打印引用
* @param content 内容
*/
public void printQuote(String content) {
System.out.println("> " + content + "\n");
}
}

代码测试

1
2
3
4
5
6
7
8
import example.PageMaker;

/** 测试外观模式 */
public class Test {
public static void main(String[] args) {
PageMaker.makeWelcomePage();
}
}

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
## 欢迎页面
> 软件设计模式(Design pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。

上个世纪 90 年代,Eric Gamma、Richard Helm、Ralph Johnson、John Vlissides 等 4 人合作出版了《设计模式:可复用面向对象软件的基础》。而 “设计模式” 这个概念也是从中而来。这 4 人也被称为 the Gang of Four,简称 GoF。

“设计模式” 共 23 种,是一种能够提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。它是解决某些特定问题的一般性概念,能够根据需求进行调整,用于解决代码中反复出现的设计问题。

合理利用 “设计模式” 开发会给我们带来很多的便利。而如何合理的运用就在于学会 “找到变化,封装变化”。

## 其他链接
* [博客主页](https://hellovie.github.io)
* [必应](https://cn.bing.com)
* [Github](https://github.com)
* [哔哩哔哩](https://www.bilibili.com)

常用场景和解决方案

  • 减少客户端处理的系统数量或联合更多的系统来扩展原有系统。
  • 让一个系统(或对象)为多个系统(或对象)工作。
  • 简化复杂系统。如果你需要一个指向复杂子系统的直接接口,且该接口的功能有限,则可以使用外观模式。
  • 作为一个简洁的中间层。如果子系统组织为多层结构,你可以要求子系统仅使用外观来进行交互, 以减少子系统之间的耦合。

模式的优缺点

优点 缺点
你可以让自己的代码独立于复杂子系统。 外观可能成为与程序中所有类都耦合的上帝对象。

使用外观模式的优势

  • 对使用者屏蔽子系统的细节,因而减少了使用者处理的对象数目,让整个系统使用起来更加方便。
  • 实现了子系统与使用者之间的松散耦合关系。
  • 有助于建立层次结构系统,并简化层与层之间的依赖关系。
  • 能够消除复杂的循环依赖。
  • 有利于系统在不同平台之间的移植和重构。

使用外观模式的劣势

  • 减低了可靠性,可能会出现过多子系统依赖一个外观系统。
  • 容易导致子系统越来越复杂。

拓展知识

  • 外观模式为现有对象定义了一个新接口,适配器模式则会试图运用已有的接口。适配器通常只封装一个对象,外观通常会作用于整个对象子系统上。
  • 外观类通常可以转换为单例模式类,因为在大部分情况下一个外观对象就足够了。


🔙 设计模式

📌最后:希望本文能够给您提供帮助,文章中有不懂或不正确的地方,请在下方评论区💬留言!

🔗参考文献:

🌐 设计模式 –refactoringguru

▶️ bilibili-趣学设计模式;黄靖锋. –拉勾教育

📖 图解设计模式 /(日)结城浩著;杨文轩译. –北京:人民邮电出版社,2017.1