十五、Facade 模式:简单窗口
程序越来越大,类错综复杂,可以为这个大型程序准备一个"窗口",这样就不必单独地关注每个类了,只需简单地对"窗口"提出请求即可。
这个"窗口"就是Facade模式。
Facade是一个源自法语Facade的单词,它的意思是"建筑物的正面"。
使用Facade模式可以为互相关联在一起的错综复杂的类整理出高层接口(API)。其中的Facade角色可以让系统对外只有一个简单的接口(API)。
而且,Facade角色还会考虑到系统内部各个类之间的责任关系和依赖关系,按照正确的顺序调用各个类。
示例要编写简单的Web页面。
在此仅考虑一个由3个简单的类构成的系统:
一个用于从邮件地址中获取用户名字的数据库类(Database),
一个用于编写HTML文件的类(Htm1Writer),
一个扮演Facade角色并提供高层接口(API)的类(PageMaker)。
在浏览器中查看到的使用示例程序编写出的Web页面如下:
![](https://i-blog.csdnimg.cn/direct/9f093c34d5ff4c7586f40958134d743d.png)
示例程序类图
![](https://i-blog.csdnimg.cn/direct/c7c4c2520680459c9def71175990bd14.png)
Database类
可获取指定数据库"名(如maildata)所对应的Properties的实例。
我们无法生成该类的任何实例,只能通过它的getProperties静态方法获取Properties的实例。
HtmlWriter类
该中隐藏着一个限制条件:必须首先调用title方法。
窗口类PageMaker使用HtmlWriter类时必须严格遵守这个限制条件。
PageMaker类一手包办了调用HtmlWriter类的方法这一工作。对外部,它只提供了makeWelcomePage接口。这就是一个简单窗口。
源文件结构
![](https://i-blog.csdnimg.cn/direct/348a68657b904563aae94723a3bf5e87.png)
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");
}
}
角色
![](https://i-blog.csdnimg.cn/direct/1bab96035a0f4a21b5df7c5e038019e3.png)
-
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模式了。即不让各个对象之间互相通信,而是增加一个仲裁者角色,让他们各自与仲裁者通信。然后,将控制显示的逻辑处理交给仲裁者负责。
示例程序类图
![](https://i-blog.csdnimg.cn/direct/6d46c090bee345fda1ae46c1274da224.png)
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");
}
}
角色
![](https://i-blog.csdnimg.cn/direct/7c73be41ff94447abfc6b4f6f57cccee.png)
-
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角色之间的通信。