《图解设计模式》笔记(七)简单化

embedded/2025/2/13 18:14:34/

十五、Facade 模式:简单窗口

程序越来越大,类错综复杂,可以为这个大型程序准备一个“窗口”,这样就不必单独地关注每个类了,只需简单地对“窗口”提出请求即可。
这个“窗口”就是Facade模式。
Facade是一个源自法语Facade的单词,它的意思是“建筑物的正面”。
使用Facade模式可以为互相关联在一起的错综复杂的类整理出高层接口(API)。其中的Facade角色可以让系统对外只有一个简单的接口(API)。
而且,Facade角色还会考虑到系统内部各个类之间的责任关系和依赖关系,按照正确的顺序调用各个类。

示例要编写简单的Web页面。

在此仅考虑一个由3个简单的类构成的系统:

一个用于从邮件地址中获取用户名字的数据库类(Database),

一个用于编写HTML文件的类(Htm1Writer),

一个扮演Facade角色并提供高层接口(API)的类(PageMaker)。

在浏览器中查看到的使用示例程序编写出的Web页面如下:

在这里插入图片描述

示例程序类图

在这里插入图片描述

Database类

可获取指定数据库”名(如maildata)所对应的Properties的实例。
我们无法生成该类的任何实例,只能通过它的getProperties静态方法获取Properties的实例。

HtmlWriter类

该中隐藏着一个限制条件:必须首先调用title方法。
窗口类PageMaker使用HtmlWriter类时必须严格遵守这个限制条件。
PageMaker类一手包办了调用HtmlWriter类的方法这一工作。对外部,它只提供了makeWelcomePage接口。这就是一个简单窗口。

源文件结构

在这里插入图片描述

Database

package pagemaker;import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;public class Database {private Database() {    // 防止外部new出Database的实例,所以声明为private方法}public static Properties getProperties(String dbname) { // 根据数据库名获取PropertiesString filename = dbname + ".txt";Properties prop = new Properties();try {prop.load(new FileInputStream(filename));} catch (IOException e) {System.out.println("Warning: " + filename + " is not found.");}return prop;}
}

HtmlWriter

package pagemaker;import java.io.Writer;
import java.io.IOException;public class HtmlWriter {private Writer writer;public HtmlWriter(Writer writer) {  // 构造函数this.writer = writer;}public void title(String title) throws IOException {    // 输出标题writer.write("<html>");writer.write("<head>");writer.write("<title>" + title + "</title>");writer.write("</head>");writer.write("<body>\n");writer.write("<h1>" + title + "</h1>\n");}public void paragraph(String msg) throws IOException {  // 输出段落writer.write("<p>" + msg + "</p>\n");}public void link(String href, String caption) throws IOException {  // 输出超链接paragraph("<a href=\"" + href + "\">" + caption + "</a>");}public void mailto(String mailaddr, String username) throws IOException {   //  输出邮件地址 link("mailto:" + mailaddr, username);}public void close() throws IOException {    // 结束输出HTMLwriter.write("</body>");writer.write("</html>\n");writer.close();}
}

maildata.txt

hyuki@hyuki.com=Hiroshi Yuki
hanako@hyuki.com=Hanako Sato
tomura@hyuki.com=Tomura
mamoru@hyuki.com=Mamoru Takahashi

PageMaker

package pagemaker;import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;public class PageMaker {private PageMaker() {   // 防止外部new出PageMaker的实例,所以声明为private方法}public static void makeWelcomePage(String mailaddr, String filename) {try {Properties mailprop = Database.getProperties("maildata");String username = mailprop.getProperty(mailaddr);HtmlWriter writer = new HtmlWriter(new FileWriter(filename));writer.title("Welcome to " + username + "'s page!");writer.paragraph("欢迎来到" + username + "的主页。");writer.paragraph("等着你的邮件哦!");writer.mailto(mailaddr, username);writer.close();System.out.println(filename + " is created for " + mailaddr + " (" + username + ")");} catch (IOException e) {e.printStackTrace();}}
}

Main

import pagemaker.PageMaker;public class Main {public static void main(String[] args) {PageMaker.makeWelcomePage("hyuki@hyuki.com", "welcome.html");}
}

角色

在这里插入图片描述

  • Facade(窗口)
    代表构成系统的许多其他角色的“简单窗口”。
    Facade角色向系统外部提供高层接口(API)。
    示例中是PageMaker类。

  • 构成系统的许多其他角色
    这些角色各自完成自己的工作,它们并不知道 Facade角色。
    Facade角色调用其他角色进行工作,但是其他角色不会调用Facade角色。
    示例中是Database类和Htm1Writer类。

  • Client(请求者)
    负责调用Facade角色。示例中是Main类。

拓展思路的要点

Facade角色到底做什么工作

Facade模式可以让复杂的东西看起来简单。
“复杂的东西”是指:在后台工作的这些类之间的关系和它们的使用方法。
程序中的类和方法很多,很难决定使用哪个,调用顺序也要注意,因此有一个能够使接口(API)变少的Facade角色很棒。
接口(API)变少了,程序与外部的关联关系弱化了,包(类的集合)更容易作为组件被复用。
设计时要注意字段方法的可见性(public),设计包也是。

递归地使用Facade 模式

假设现在有几个持有Facade角色的类的集合,可通过整合这几个集合来引人新的Facade角色。
即,我们可以递归地使用 Facade模式。
在超大系统中,往往都含有非常多的类和包。在每个关键的地方都使用 Facade模式,便于维护系统。

开发人员不愿意创建Facade角色的原因——心理原因

通常,熟悉系统内部复杂处理的开发人员可能不太愿意创建Facade角色,可能是因为对熟练的开发人员而言,他们对类之间的所有相互依赖关系都一清二楚。
当面对“在调用那个类之前需要先调用这个类。在调用那个方法之前需要先在这个类中注册一下”时,意味着需要引入 Facade角色了。

相关的设计模式

  • Abstract Factory 模式(第8章)
    可以将 Abstract Factory模式看作生成复杂实例时的 Facade模式。
    因为它提供了“要想生成这个实例只需要调用这个方法就OK了”的简单接口。

  • Singleton模式(第5章)
    有时会使用 Singleton模式创建Facade角色。

  • Mediator模式(第16章)
    在 Facade模式中,Facade角色单方面地使用其他角色来提供高层接口(API)。
    而在Mediator模式中,Mediator角色作为 Colleague角色间的仲裁者负责调停。
    可以说,Facade模式是单向的,而Mediator角色是双向的。

十六、Mediator 模式:只有一个仲裁者

Mediator 模式:整个团队的交流过程就变为了组员向仲裁者报告,仲裁者向组员下达指示。组员之间不再相互询问和相互指示。

示例程序是一个GUI应用程序,它展示了一个登录对话框,用户在输入正确的用户名和密码后可以登录。

登录角色(游客或用户)、用户名输入框、密码输入框、OK按钮,它们之间有一定的逻辑关系,不详细展开描述了

像上面这样要调整多个对象之间的关系时,就需要用到Mediator模式了。即不让各个对象之间互相通信,而是增加一个仲裁者角色,让他们各自与仲裁者通信。然后,将控制显示的逻辑处理交给仲裁者负责。

示例程序类图

在这里插入图片描述

Mediator

public interface Mediator {public abstract void createColleagues();public abstract void colleagueChanged();
}

Colleague

public interface Colleague {public abstract void setMediator(Mediator mediator);public abstract void setColleagueEnabled(boolean enabled);
}

ColleagueButton

import java.awt.Button;public class ColleagueButton extends Button implements Colleague {private Mediator mediator;public ColleagueButton(String caption) {super(caption);}public void setMediator(Mediator mediator) {            // 保存Mediatorthis.mediator = mediator;}public void setColleagueEnabled(boolean enabled) {      // Mediator下达启用/禁用的指示 setEnabled(enabled);}
}

ColleagueTextField

import java.awt.TextField;
import java.awt.Color;
import java.awt.event.TextListener;
import java.awt.event.TextEvent;public class ColleagueTextField extends TextField implements TextListener, Colleague {private Mediator mediator;public ColleagueTextField(String text, int columns) {   // 构造函数super(text, columns);}public void setMediator(Mediator mediator) {            // 保存Mediatorthis.mediator = mediator;}public void setColleagueEnabled(boolean enabled) {      // Mediator下达启用/禁用的指示setEnabled(enabled);setBackground(enabled ? Color.white : Color.lightGray);}public void textValueChanged(TextEvent e) {             // 当文字发生变化时通知Mediatormediator.colleagueChanged();}
}

ColleagueCheckbox

import java.awt.Checkbox;
import java.awt.CheckboxGroup;
import java.awt.event.ItemListener;
import java.awt.event.ItemEvent;public class ColleagueCheckbox extends Checkbox implements ItemListener, Colleague {private Mediator mediator;public ColleagueCheckbox(String caption, CheckboxGroup group, boolean state) {  // 构造函数 super(caption, group, state);}public void setMediator(Mediator mediator) {            // 保存Mediatorthis.mediator = mediator;}public void setColleagueEnabled(boolean enabled) {      // Mediator下达启用/禁用指示setEnabled(enabled);}public void itemStateChanged(ItemEvent e) {             // 当状态发生变化时通知Mediatormediator.colleagueChanged();}
}

LoginFrame

import java.awt.Frame;
import java.awt.Label;
import java.awt.Color;
import java.awt.CheckboxGroup;
import java.awt.GridLayout;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;public class LoginFrame extends Frame implements ActionListener, Mediator {private ColleagueCheckbox checkGuest;private ColleagueCheckbox checkLogin;private ColleagueTextField textUser;private ColleagueTextField textPass;private ColleagueButton buttonOk;private ColleagueButton buttonCancel;// 构造函数。// 生成并配置各个Colleague后,显示对话框。public LoginFrame(String title) {super(title);setBackground(Color.lightGray);// 使用布局管理器生成4×2窗格setLayout(new GridLayout(4, 2));// 生成各个ColleaguecreateColleagues();// 配置:将它们保存在LoginFrame类的字段中add(checkGuest);add(checkLogin);add(new Label("Username:"));add(textUser);add(new Label("Password:"));add(textPass);add(buttonOk);add(buttonCancel);// 设置初始的启用起用/禁用状态colleagueChanged();// 显示pack();show();}// 生成各个Colleague。public void createColleagues() {// 生成CheckboxGroup g = new CheckboxGroup();checkGuest = new ColleagueCheckbox("Guest", g, true);checkLogin = new ColleagueCheckbox("Login", g, false);textUser = new ColleagueTextField("", 10);textPass = new ColleagueTextField("", 10);textPass.setEchoChar('*');buttonOk = new ColleagueButton("OK");buttonCancel = new ColleagueButton("Cancel");// 设置Mediator:调用每个Colleague的setMediator方法,事先告知它们“我是仲裁者,有什么问题的可以向我报告”。checkGuest.setMediator(this);checkLogin.setMediator(this);textUser.setMediator(this);textPass.setMediator(this);buttonOk.setMediator(this);buttonCancel.setMediator(this);// 设置各个Colleague的Listener:这样,AWT框架就可以调用合适的Listener了。checkGuest.addItemListener(checkGuest);checkLogin.addItemListener(checkLogin);textUser.addTextListener(textUser);textPass.addTextListener(textPass);buttonOk.addActionListener(this);buttonCancel.addActionListener(this);}// 接收来自于Colleage的通知然后判断各Colleage的启用/禁用状态。public void colleagueChanged() {if (checkGuest.getState()) { // Guest modetextUser.setColleagueEnabled(false);textPass.setColleagueEnabled(false);buttonOk.setColleagueEnabled(true);} else { // Login modetextUser.setColleagueEnabled(true);userpassChanged();}}// 当textUser或是textPass文本输入框中的文字发生变化时// 判断各Colleage的启用/禁用状态private void userpassChanged() {if (textUser.getText().length() > 0) {textPass.setColleagueEnabled(true);if (textPass.getText().length() > 0) {buttonOk.setColleagueEnabled(true);} else {buttonOk.setColleagueEnabled(false);}} else {textPass.setColleagueEnabled(false);buttonOk.setColleagueEnabled(false);}}public void actionPerformed(ActionEvent e) {System.out.println(e.toString());System.exit(0);}
}

Main

import java.awt.*;
import java.awt.event.*;public class Main {static public void main(String args[]) {new LoginFrame("Mediator Sample");}
}

角色

在这里插入图片描述

  • Mediator(仲裁者、中介者)
    定义与Colleague角色进行通信和做出决定的接口(API)。
    示例中是Mediator接口。

  • ConcreteMediator(具体的仲裁者、中介者)
    ConcreteMediator角色负责实现 Mediator角色的接口(API),负责实际做出决定。
    示例中是LoginFrame类。

  • Colleague(同事)
    Colleague角色负责定义与Mediator角色进行通信的接口(API)。
    示例中是Colleague接口。

  • ConcreteColleague(具体的同事)
    ConcreteColleague角色负责实现Colleague角色的接口(API)。
    示例中是ColleagueButton类、ColleagueTextField类和ColleagueCheckbox类。

拓展思路的要点

当发生分散灾难时

示例程序中的LoginFrame类的colleagueChanged方法稍复杂。
需求变更时该方法易出Bug,但调试该方法很好定位原因,因为其他地方并没有控制控件的启用/禁用状态的逻辑处理。
如果这段逻辑分散在ColleagueButton类、ColleagueTextField类和ColleagueCheckbox类中,那么很难编写、调试、修改代码。
通常,面向对象编程可以帮助我们分散处理,避免处理过于集中,也就是说可以“分而治之”。
但是在示例中,分散在各个类中处理并不明智。
应分散时分散,应集中时集中,视情况而定。

通信线路的增加

若有A和B这2个实例,互相通信(相互之间调用方法),则通信线路有两条:A→B和A-B。
若有A、B和C这3个实例,则有6条通信线路:A→B、A←B、B→C、B←C、C→A和C←A。
若有4个、5个……程序结构会变得非常复杂。
若因最初实例很少而没用本模式,很可能随着需求变更实例数量慢慢变多,迟早会暴露出问题。

哪些角色可以复用

ConcreteColleague角色可以复用,但ConcreteMediator角色很难复用。
例如,现在要制作另外一个对话框。
这时,我们可将扮演ConcreteColleague角色的ColleagueButton类、ColleagueTextField类和 ColleagueCheckbox类用于新的对话框中。
因为在 ConcreteColleague角色中并没有任何依赖于特定对话框的代码。

相关的设计模式

  • Facade模式(第15章)
    在 Mediator模式中,Mediator角色与 Colleague角色进行交互。
    而在 Facade模式中,Facade角色单方面地使用其他角色来对外提供高层接口(API)。
    因此,可以说 Mediator模式是双向的,而 Facade模式是单向的。

  • Observer模式(第17章)
    有时会使用 Observer模式来实现 Mediator角色与 Colleague角色之间的通信。


http://www.ppmy.cn/embedded/161926.html

相关文章

IntelliJ IDEA 安装与使用完全教程:从入门到精通

一、引言 在当今竞争激烈的软件开发领域&#xff0c;拥有一款强大且高效的集成开发环境&#xff08;IDE&#xff09;是开发者的致胜法宝。IntelliJ IDEA 作为 JetBrains 公司精心打造的一款明星 IDE&#xff0c;凭借其丰富多样的功能、智能精准的代码提示以及高效便捷的开发工…

git提交到GitHub问题汇总

1.main->master git默认主分支是maser&#xff0c;如果是按照这个分支名push&#xff0c;GitHub会出现两个branch&#xff0c;与预期不符 解决方案&#xff1a;更改原始主分支名为main git config --global init.defaultBranch main2.git&#xff1a;OpenSSL SSL_read: SS…

【Stable Diffusion模型测试】测试ControlNet,没有线稿图?

相信很多小伙伴跟我一样&#xff0c;在测试Stable Diffusion的Lora模型时&#xff0c;ControlNet没有可输入的线稿图&#xff0c;大家的第一反应就是百度搜&#xff0c;但是能从互联网上搜到的高质量线稿图&#xff0c;要么收费&#xff0c;要么质量很差。 现在都什么年代了&a…

Anaconda Navigator 与 Conda:GUI 和 CLI 的对比与使用

1. 引言 Anaconda 提供了两种主要的管理工具&#xff1a; Anaconda Navigator&#xff08;GUI 界面&#xff09;Conda&#xff08;命令行工具 CLI&#xff09; 这两种工具各有优劣&#xff0c;适用于不同类型的用户。本文将详细介绍它们的功能、使用方法及对比分析&#xff…

Web3 的虚实融合之路:从虚拟交互到元宇宙构建

在这个数字技术日新月异的时代&#xff0c;我们正站在 Web3 的门槛上&#xff0c;见证着互联网的又一次革命。Web3 不仅仅是技术的迭代&#xff0c;它代表了一种全新的交互方式和价值创造模式。本文将探讨 Web3 如何推动虚拟交互的发展&#xff0c;并最终实现元宇宙的构建&…

Zabbix-监控SSL证书有效期

背景 项目需要&#xff0c;需要监控所有的SSL证书的有效期&#xff0c;因此需要自定义一个监控项 实现 创建自定义脚本 在Zabbix的scripts目录(/etc/zabbix/scripts/)下创建一个新的shell脚本check_ssl.sh&#xff0c;内容如下 #!/bin/bash time$(echo | openssl s_client…

OpenWRT中常说的LuCI是什么——LuCI介绍(一)

我相信每个玩openwrt的小伙伴都或多或少看到过luci这个东西&#xff0c;但luci到底是什么东西&#xff0c;可能还不够清楚&#xff0c;今天就趁机来介绍下&#xff0c;openwrt中的luci&#xff0c;到底是个什么东西。 什么是LuCI&#xff1f; 首先&#xff0c;LuCI是OpenWRT中…

零基础玩转 DeepSeek API实战教程

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于大模型算法的研究与应用。曾担任百度千帆大模型比赛、BPAA算法大赛评委,编写微软OpenAI考试认证指导手册。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。授权多项发明专利。对机器学…