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

十五、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

java 复制代码
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

java 复制代码
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

properties 复制代码
hyuki@hyuki.com=Hiroshi Yuki
hanako@hyuki.com=Hanako Sato
tomura@hyuki.com=Tomura
mamoru@hyuki.com=Mamoru Takahashi

PageMaker

java 复制代码
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

java 复制代码
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

java 复制代码
public interface Mediator {
    public abstract void createColleagues();
    public abstract void colleagueChanged();
}

Colleague

java 复制代码
public interface Colleague {
    public abstract void setMediator(Mediator mediator);
    public abstract void setColleagueEnabled(boolean enabled);
}

ColleagueButton

java 复制代码
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

java 复制代码
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

java 复制代码
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

java 复制代码
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

java 复制代码
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角色之间的通信。

相关推荐
siy23331 小时前
VS2022中cmath.h头文件功能介绍
c语言·笔记·学习
陈志化1 小时前
kafka了解-笔记
笔记·分布式·kafka
new6669991 小时前
Java实现模版方法模式
java·设计模式
jayxlb21 小时前
LLaMA-Factory 安装linux部署&conda笔记
笔记·ai·llama
向上的车轮2 小时前
OpenEuler学习笔记(二十三):在OpenEuler上部署开源MES系统
linux·笔记·学习·开源
艾斯特_3 小时前
前端设计模式介绍及案例(单例模式、代理模式、工厂模式、装饰者模式、观察者模式)
前端·javascript·观察者模式·单例模式·设计模式
茕离3 小时前
b站——《【强化学习】一小时完全入门》学习笔记及代码(1-3 多臂老虎机)
人工智能·笔记·学习
自动驾驶小白说3 小时前
【强化学习入门笔记】3.2 策略梯度法:REINFORCE
笔记
weixin_438335403 小时前
设计模式之策略模式
设计模式·策略模式
一个 00 后的码农3 小时前
25自动化考研复试面试常见核心问题真题汇总,自动化考研复试面试有哪些经典问题?自动化考研复试难不难啊?
经验分享·笔记·考研·面试