Author: Gorit
Date:2021年11月16日
Refer:《图解设计模式》

15.1 Facade 模式

程序会随着时间推移,不断的更新迭代。从而导致程序内部关系越来越复杂,程序结构也越来越复杂。我们要使用每个类之前,都要弄清楚它们之间的依赖关系,以及正确调用顺序。

在调用大型应用处理时,我们需要格外的注意那些数据庞大的类之间复杂的关系。其实我们可以设计一个这样的“窗口”。这样的话,我们就不必关注每个类了,只需要对“窗口”发出请求即可。

这个“窗口”便是本节中要学习的 Facade 模式。

Facade 模式解释:

  1. 使用 Facade 模式可以为互相关联在一起错综复杂的类整理出高层接口(API)
  2. Facade 角色可以让系统系统对外只有一个简单的接口。
  3. Facade 角色 会考虑到系统内部各个类之间的责任关系和依赖关系,按照正确的顺序调用各个类。

15.2 示例程序

说明
pagemaker Database 从邮件地址获取用户名的类
pagemaker HtmlWriter 编写 HTML 文件的类
pagemaker PageMaker 根据邮件地址编写该用户的 Web 界面
Main 测试程序行为的类

Database 类

  1. package Facade.pagemaker;
  2. import java.io.FileInputStream;
  3. import java.io.IOException;
  4. import java.util.Properties;
  5. /**
  6. * @Author Gorit
  7. * @Date 2021/11/20
  8. * 可以获取指定数据库名(这里指txt 文本,而非关系型数据库) 所对应的 Properties 实例。
  9. * 我们无法生成该类的实例,只能通过它的 getProperties 静态方法获取 Properties 实例
  10. **/
  11. public class Database {
  12. private Database () { // 防止外部 new 出 Database 实例,所有声明为 private
  13. }
  14. /**
  15. * @description 根据数据库名获取 Properties
  16. * @param dbname
  17. * @return prop
  18. */
  19. public static Properties getProperties(String dbname) {
  20. String fileName = "./mockData/" +dbname + ".txt";
  21. Properties prop = new Properties();
  22. try {
  23. prop.load(new FileInputStream(fileName));
  24. } catch (IOException e) {
  25. System.out.println("Warning: " + fileName + " is not found.");
  26. }
  27. return prop;
  28. }
  29. }

配置文件:(.\mockData\maildata)

  1. hyuki@hyuki.com=Hiroshi Yuki
  2. hanako@hyuki.com=Hanako Stao
  3. tomura@hyuki.com=Tomura
  4. manoru@hyuki.com=Mamoru Takahashi

HtmlWriter类

  1. package Facade.pagemaker;
  2. import java.io.IOException;
  3. import java.io.Writer;
  4. /**
  5. * @Author Gorit
  6. * @Date 2021/11/20
  7. * 该类用于编写简单的 web 页面
  8. **/
  9. public class HtmlWriter {
  10. private Writer writer; // 生成 HtmlWriter 类实例时,赋予 Writer,使用 Writer 输出 HTML
  11. public HtmlWriter(Writer writer) {
  12. this.writer = writer;
  13. }
  14. // title 表示 HTML 的标题
  15. public void title(String title) throws IOException {
  16. writer.write("<html>");
  17. writer.write(" <head>\n");
  18. writer.write(" <title>" + title + "</title>\n");
  19. writer.write(" </head>");
  20. writer.write("<body>\n");
  21. writer.write("<h1>" + title + "</h1>\n");
  22. }
  23. // 输出段落
  24. public void paragraph(String msg) throws IOException {
  25. writer.write("<p>" + msg + "</p>\n");
  26. }
  27. // 输出超链接
  28. public void link(String href, String caption) throws IOException {
  29. paragraph("<a href=\"" + href + "\">" + caption + "</a>\n");
  30. }
  31. // 输出邮件地址
  32. public void mailto(String mailaddr, String username) throws IOException {
  33. link("mailto:" + mailaddr, username);
  34. }
  35. // 结束输出 HTML
  36. public void close() throws IOException {
  37. writer.write("</body>\n");
  38. writer.write("</html>");
  39. writer.close();
  40. }
  41. }

PageMaker类

  1. package Facade.pagemaker;
  2. import java.io.FileWriter;
  3. import java.io.IOException;
  4. import java.util.Properties;
  5. /**
  6. * @Author Gorit
  7. * @Date 2021/11/20
  8. * 通过 HtmlWriter 和 Database 类为目标生成 Web 页面
  9. *
  10. * !!!
  11. * PageMaker 类一手包办了调用 HtmlWriter 类的方法这一工作。
  12. * 对外部,它只提供了 makeWelcomePage 接口
  13. **/
  14. public class PageMaker {
  15. private PageMaker() {} // 防止外部 new 出实例
  16. /**
  17. * 根据指定邮件地址和文件名生成对应的 Web 页面
  18. * @param mailaddr
  19. * @param filename
  20. */
  21. public static void makeWelcomePage(String mailaddr, String filename) {
  22. try {
  23. Properties mailProp = Database.getProperties("maildata");
  24. String username = mailProp.getProperty(mailaddr);
  25. HtmlWriter writer = new HtmlWriter(new FileWriter("./mockData/" + filename));
  26. writer.title("Welcome to " + username + "'s page!");
  27. writer.paragraph(username + " 欢迎来到 " + username + "的主页。");
  28. writer.paragraph("等着你爹邮件!");
  29. writer.mailto(mailaddr, username);
  30. writer.close();
  31. System.out.println(filename + " is created for " + mailaddr + " (" + username + ")");
  32. } catch (IOException e) {
  33. e.printStackTrace();
  34. }
  35. }
  36. }

Main类

  1. package Facade;
  2. import Facade.pagemaker.PageMaker;
  3. /**
  4. * @Author Gorit
  5. * @Date 2021/11/21
  6. *
  7. * mockData:
  8. * hyuki@hyuki.com=Hiroshi Yuki
  9. * hanako@hyuki.com=Hanako Stao
  10. * tomura@hyuki.com=Tomura
  11. * manoru@hyuki.com=Mamoru Takahashi
  12. **/
  13. public class Main {
  14. public static void main(String[] args) {
  15. PageMaker.makeWelcomePage("hanako@hyuki.com", "welcome.html");
  16. }
  17. }

15.3 Facade 模式中登场的角色

一、Facade 角色到底是干啥的?

Facade 模式可以让复杂的东西看起来简单。但是,这里说到的“复杂的东西”是什么呢?其实是在关注后台工作中的这些类之间和它们的使用方法。使用 Facade 模式可以让我们不必在意这些复杂的东西。

我们观察 Main 函数可以发现,我们通过一行代码就实现了创建 “welcome.html”。同时我们可以调用的 API 接口变少了。但是如果我们这个类很庞大,很复杂。类之间的调用顺序也很容易弄错。因此需要格外注意。所以,可以使 API 接口调用变少的 Facade 角色是多么重要。

接口变少了,意味着程序与外部之间的联系弱化了,这样更容易使我们的包(类的集合)作为组件被复用。

二、递归使用 Facade 模式

假设现在有几个持有 Facade 角色的类的集合,那么我们可以整合几个集合来引入新的 Facade 角色。也就是说我们可以递归使用 Facade 模式

在超大系统中,会包含非常多的类和包,如果我们在每个关键地方都使用 Facade 模式,那么系统维护起来就会变得容易的多。

15.4 相关设计模式

一、Abstract Factory 模式

可以将 Abstract Factory 模式看作生成复杂实例的 Facade 模式。它提供了一个“想要生成这个实例只需要调用这个方法就 ok 了”的简单接口

二、Singleton 模式

有时采用 Singleton 模式创建 Facade 角色(构造方法私有化)

三、Mediator 模式

在 Facade 模式中,Facade 角色单方面使用其他角色来提供高层接口(API)

在 Mediator 模式,Mediator 角色作为 Colleague 角色间的仲裁者负责调停。
Facade 模式是单向的,Mediator 是双向的