当前位置: 首页 > article >正文

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

十五、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) { // 根据数据库名获取Properties
        String 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 {    // 结束输出HTML
        writer.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) {            // 保存Mediator
        this.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) {            // 保存Mediator
        this.mediator = mediator;
    }
    public void setColleagueEnabled(boolean enabled) {      // Mediator下达启用/禁用的指示
        setEnabled(enabled);
        setBackground(enabled ? Color.white : Color.lightGray);
    }
    public void textValueChanged(TextEvent e) {             // 当文字发生变化时通知Mediator
        mediator.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) {            // 保存Mediator
        this.mediator = mediator;
    }
    public void setColleagueEnabled(boolean enabled) {      // Mediator下达启用/禁用指示
        setEnabled(enabled);
    }
    public void itemStateChanged(ItemEvent e) {             // 当状态发生变化时通知Mediator
        mediator.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));
        // 生成各个Colleague
        createColleagues();
        // 配置:将它们保存在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 mode
            textUser.setColleagueEnabled(false);
            textPass.setColleagueEnabled(false);
            buttonOk.setColleagueEnabled(true);
        } else { // Login mode
            textUser.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.kler.cn/a/542180.html

相关文章:

  • 本地部署DeepSeek R1 + 界面可视化open-webui
  • aspectFill(填充目标区域的同时保持图像的原有宽高比 (aspect ratio)图像不会被拉伸或压缩变形
  • 个人毕业设计--基于HarmonyOS的旅行助手APP的设计与实现(挖坑)
  • spring boot接收请求常用注解
  • fps动作系统10:右键机瞄
  • CSS3+动画
  • LeetCode刷题---数组---840
  • 力扣1448. 统计二叉树中好节点的数目
  • 机器学习实战(零基础到精通)
  • deepseek本地部署小白教程
  • JavaEE-前端与后台的搭建
  • 【Mastering Vim 2_01】开篇词:在 AI 时代持续深耕底层技术,做长期主义的坚定捍卫者
  • 4.混合推荐系统
  • 数据结构 单链表的模拟实现
  • CSDN 大模型 笔记
  • 关于浏览器缓存的思考
  • MAAS | DeepSeek本地部署如何开启联网搜索?
  • vue 134~152
  • win10右键使用IDEA打开
  • Kafka的架构解析
  • 【DeepSeek】在本地计算机上部署DeepSeek-R1大模型实战(完整版)
  • 语法备忘04:将 事件处理函数 绑定到 组件 的事件上
  • android 默认开启位置信息中WLAN扫描和蓝牙扫描
  • 文理医院预约挂号系统的设计与实现(代码+数据库+LW)
  • 查询语句来提取 detail 字段中包含 xxx 的 URL 里的 commodity/ 后面的数字串
  • 数据结构与算法-动态规划-状态机(股票问题,密码设计)