Facade 模式
这个模式其实很简单, 就是如果一个程序内部很复杂, 与其对外要求程序的使用者先做这个在做这个, 不如用一个东西包装一下, 用一个窗口对外即可. 用术语就是将互相关联在一起的类整理出高层接口API, Facade角色来具备这个API, 对外只需要暴露Facade角色即可. 作者的例子也很简单, 是一个生成HTML页面的类. 类里有一个生成段落和生成HTML结构的两个类, 这里隐含着必须先生成title等内容, 才能再生成正文的顺序. 如果将类交给用户使用, 用户就必须注意其中的顺序, 然而我们可以包装一个类, 只需要名字和文件名, 就可以隐藏其中的细节, 直接生成HTML文件. 先来看生成名字的类:import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
public class Database {
private Database() {
}
public static Properties getProperties(String name) {
String filename = name + ".txt";
Properties properties = new Properties();
try {
properties.load(new FileInputStream(filename));
} catch (IOException e) {
System.out.println("加载文件失败");
}
return properties;
}
}
这个类是一个通过文件加载Properties的类, 很简单. 然后是写HTML的类, 之前已经很多次编写过类似的类了:
import java.io.IOException;
import java.io.Writer;
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>");
writer.write(title);
writer.write("</title>");
writer.write("</head>");
writer.write("<body>\n");
writer.write("<h1>\n");
writer.write(title);
writer.write("</h1>");
}
public void paragraph(String msg) throws IOException {
writer.write("<p>");
writer.write(msg);
writer.write("</p>");
}
public void link(String href, String caption) throws IOException {
paragraph("<a href=\"" + href + "\">" + caption + "</a>");
}
public void mailto(String address, String username) throws IOException {
link("mailto:" + address, username);
}
public void close() throws IOException {
writer.write("</body>");
writer.write("</html>\n");
writer.close();
}
}
其中拼接HTML都是传统艺能了, 这里主要是看一个顺序, 即必须先生成头部信息, 然后填充正文才行. 如果交给用户自己使用这个类, 则用户必须按照这个顺序调用.
现在就该Facade角色出场了, 我们编写一个PageMaker类, 使用用户名然后在其中正确的调用这些方法并且生成文件:
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Properties;
public class PageMaker {
private PageMaker() {
}
public static void makeWelcomePage(String mailaddr, String filename) {
try {
Properties mailprop = Database.getProperties("maildata.txt");
String username = mailprop.getProperty(mailaddr);
HtmlWriter writer = new HtmlWriter(new PrintWriter(filename));
writer.title("Welcome to" + username + "'s page!");
writer.paragraph("waiting....");
writer.mailto(mailaddr, username);
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
这个类也很简单, 重点看其中只用了一个API就可以操作了.
public class Main {
public static void main(String[] args) {
PageMaker.makeWelcomePage("conyli@conyli.cc", "welcome.html");
}
}
这个模式我感觉其实就是最基本的封装, 而且Facade角色可以嵌套, 一层套一层来使用.
练习
习题15-1 这个很简单,如果不想要外边的包访问Database和HtmlWriter, 只需要将其权限改为包访问权限即可.习题15-2 这个也很简单, 只不过是再添加一个方法即可. 其中的title部分可以复用. 从Properties中取出所有的邮件地址和用户名来复用生成link的方法即可.
public static void makeLinkPage(String filename) {
try {
Properties mailprop = Database.getProperties("maildata");
HtmlWriter writer = new HtmlWriter(new PrintWriter(filename));
writer.title("Link Page");
for (String name : mailprop.stringPropertyNames()) {
writer.mailto(name, mailprop.getProperty(name) );
}
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
Mediator 仲裁者模式
这个模式我想了一下, 应该适用那些整体状态非常多的时候, 每个组件都需要向一个集中管理状态的组件进行报告的时候使用. 以后自己如果写前端的话, 应该可以使用到这个模式. 简单的说, 调整多个对象之间的关系, 就可以用到仲裁者模式, 即不让各个组件之间通信, 而是增加一个角色负责管理. 这里作者举了一个AWT的例子, 其本质和Web开发的理念是一样的. 其中有两个接口, 一个表示仲裁者, 一个表示成员对象.public interface Mediator {
//创建符合仲裁者模式要求的成员对象
void createColleagues();
//让组员向仲裁者报告变化的方法, 由组员调用
void colleagueChanged();
}
public interface Colleague {
//设置自己的仲裁者
void setMediator(Mediator mediator);
//设置自己的状态为是否可用, 这个方法教给仲裁者来调用, 而不是自己设置
void setColleagueEnabled(boolean enabled);
}
在这两个接口的状态下, 我们就可以来编写仲裁者和团队成员类了:
import java.awt.Button;
public class ColleagueButton extends Button implements Colleague {
private Mediator mediator;
public ColleagueButton(String caption) {
super(caption);
}
@Override
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}
//这个其实是利用了awt包里的特性, 比如setEnabled表示是否禁用
@Override
public void setColleagueEnabled(boolean enabled) {
setEnabled(enabled);
}
}
import java.awt.*;
import java.awt.event.TextEvent;
public class ColleagueTextField extends TextField implements Colleague, TextListener {
private Mediator mediator;
public ColleagueTextField(String text, int columns) {
super(text, columns);
}
@Override
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}
@Override
public void setColleagueEnabled(boolean enabled) {
setEnabled(enabled);
setBackground(enabled ? Color.white : Color.lightGray);
}
//自己的文字发生变化的时候通知仲裁者
//这是一个接口, 会在文字发生变化的时候被awt框架调用
public void textValueChanged(TextEvent event) {
mediator.colleagueChanged();
}
}
这里注意红字的实现了一个额外的接口, 这是awt框架用来检测变化事件的.下边的Checkbox也类似:
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
public class ColleagueCheckbox extends Checkbox implements Colleague, ItemListener {
private Mediator mediator;
public ColleagueCheckbox(String caption, CheckboxGroup checkboxGroup, boolean state) {
super(caption, checkboxGroup, state);
}
@Override
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}
@Override
public void setColleagueEnabled(boolean enabled) {
setEnabled(enabled);
}
public void itemStateChanged(ItemEvent event) {
mediator.colleagueChanged();
}
}
最后就可以来编写仲裁者类了, 会根据各个组件的通知来管理状态:
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
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;
public LoginFrame(String title) {
//绘制框体和创建组件
super(title);
setBackground(Color.lightGray);
setLayout(new GridLayout(4, 2));
createColleagues();
//添加所有组件
add(checkGuest);
add(checkLogin);
add(new Label("Username: "));
add(textUser);
add(new Label("Password: "));
add(textPass);
add(buttonOk);
add(buttonCancel);
//设置初始状态
colleagueChanged();
pack();
show();
}
@Override
public void createColleagues() {
CheckboxGroup g = new CheckboxGroup();
//这个猜着就能看懂, 一组两个checkbox, 一个默认选中 , 一个默认不选
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");
//设置仲裁者为当前对象
checkGuest.setMediator(this);
checkLogin.setMediator(this);
textUser.setMediator(this);
textPass.setMediator(this);
buttonOk.setMediator(this);
buttonCancel.setMediator(this);
//添加事件监听器, 这个应该是套路
checkGuest.addItemListener(checkGuest);
checkLogin.addItemListener(checkLogin);
textUser.addTextListener(textUser);
textPass.addTextListener(textPass);
buttonOk.addActionListener(this);
buttonCancel.addActionListener(this);
}
@Override
public void colleagueChanged() {
//如果客户被选中, 禁止两个文本框, OK按钮可以选中
if (checkGuest.getState()) {
textUser.setColleagueEnabled(false);
textPass.setColleagueEnabled(false);
buttonOk.setColleagueEnabled(true);
} else {
textUser.setColleagueEnabled(true);
//这是抽取出来的方法, 用于改变状态
userpassChanged();
}
}
private void userpassChanged() {
buttonOk.setEnabled(false);
//如果用户名中输入了字符就让密码框可以使用. 密码框可用的时候, 如果有字符就让OK键可以用, 否则OK键无法使用
if (textUser.getText().length() > 0) {
textPass.setColleagueEnabled(true);
if (textPass.getText().length() > 0) {
buttonOk.setColleagueEnabled(true);
} else {
buttonOk.setColleagueEnabled(false);
}
}
}
@Override
public void actionPerformed(ActionEvent e) {
System.out.println(e.toString());
System.exit(0);
}
}
这个逻辑还是比较好懂的, 先创建一系列控件, 然后添加好, 之后每次监听到任何事件, 比如输入文字和单选框发生变化, 都跳到colleagueChanged()方法中对逻辑进行统一处理.
使用起来就是使用awt框架:
public class Main {
public static void main(String[] args) {
new LoginFrame("Mediator Sample");
}
}
回头看看这个模式, 这个模式的关键在于, 各个控件没有更改自身的状态, 而是统一交给仲裁者来管理, 这样有一大好处就是如果出现BUG, 几乎可以确定是仲裁者编写出现问题, 而无需一个一个到各个组件中寻找错误所在.
一般仲裁者由于利用了其他类的一些具体功能特性, 使得难以复用. 不过组件一般都是可以复用的.
这个模式特别适合使用在GUI模式中, 也就意味着在编写Web前端应用的时候, 可以考虑使用该模式.
练习
练习16-1 修改成当用户名和密码的长度都大于等于4个字符的时候OK按钮才有效. 很显然, 在修改需求的时候, 仲裁者模式只需要修改仲裁者类:private void userpassChanged() {
buttonOk.setEnabled(false);
//如果用户名中输入了字符就让密码框可以使用. 密码框可用的时候, 如果有字符就让OK键可以用, 否则OK键无法使用
if (textUser.getText().length() > 0) {
textPass.setColleagueEnabled(true);
if (textPass.getText().length() >=4 && textUser.getText().length()>=4) {
buttonOk.setColleagueEnabled(true);
} else {
buttonOk.setColleagueEnabled(false);
}
}
}
练习16-2 无法在接口中定义, 因为接口中如果定义域, 只能定义静态域.