动机
如图,这是Gof《设计模式》一书第五章中Mediator中介者模式动机一节的例子。
原文:
考虑一个图形用户中对话框的实现。对话框使用一窗口来展现一系列的窗口组件,如按钮、菜单和输入域等。如下图。
实现效果
这是我的实现效果。
点击font choose按钮,弹出对话框界面。
选择效果后,点击确定。
文本区域的字体已改变。
完整代码
简要思路:
我们自定义两个类 FontChooser 和 FontChooserDialog 。
FontChooserDialog 继承自 JDialog,自定义对话框组件。
FontChooser 继承于 JFrame,自定义 JFrame 容器。
并由 FontChooser 包含一个 FontChooserDialog ,由此实现以上效果。
FontChooserDialog类
package behaviorTypePattern.mediatorPattern.GUI;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/**
* 自定义对话框
*/
public class FontChooserDialog extends JDialog {
//组件
private JTextField inputText;
private JList fontList;
private Button cancelButton;
private Button okButton;
private JRadioButton mediumButton;
private JRadioButton boldButton;
private JRadioButton romanButton;
private JRadioButton italicButton;
private JSlider sizeSlider;
//构成Font的参数值
private String name;
private int weight;
private int slant;
private int fontSize;
public FontChooserDialog(Frame parent, String title) {
//在构造函数完成自定义对话框的绘制
super(parent, title);
//采用盒子布局
Box overallBox = Box.createVerticalBox(); //负责整体布局的盒子
Box familyBox = Box.createHorizontalBox(); //负责family区域布局的盒子
Box fontListBox = Box.createHorizontalBox();//负责列表布局的盒子
Box weightBox = Box.createHorizontalBox();//负责weight区域布局的盒子
Box slantBox = Box.createHorizontalBox();//负责slant区域布局的盒子
Box buttonBox = Box.createHorizontalBox();//负责按钮布局的盒子
//输入框 创建并绑定对应盒子
inputText = new JTextField(10);
inputText.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
setName(inputText.getText());//当在inputText区域按回车时把文本赋值给name
}
});
familyBox.add(new Label("Family"));
familyBox.add(inputText);
//列表 创建并绑定对应盒子
String[] items = new String[]{"宋体","楷体"};
fontList = new JList(items);
fontList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
fontList.addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
inputText.setText((String) fontList.getSelectedValue());
}
});
fontListBox.add(fontList);
//Weight 创建并绑定对应盒子
ButtonGroup weightGroup = new ButtonGroup();
mediumButton = new JRadioButton("medium",true);
mediumButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
setWeight(Font.PLAIN);
}
});
boldButton = new JRadioButton("bold");
boldButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
setWeight(Font.BOLD);
}
});
weightGroup.add(mediumButton);
weightGroup.add(boldButton);
weightBox.add(mediumButton);
weightBox.add(boldButton);
//Slant 创建并绑定对应盒子
ButtonGroup slantGroup = new ButtonGroup();
romanButton = new JRadioButton("roman",true);
romanButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
setSlant(Font.PLAIN);
}
});
italicButton = new JRadioButton("italic");
italicButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
setSlant(Font.ITALIC);
}
});
slantGroup.add(romanButton);
slantGroup.add(italicButton);
slantBox.add(romanButton);
slantBox.add(italicButton);
//sizeSlider 创建并绑定对应盒子
Box sizeBox = Box.createHorizontalBox();
sizeSlider = new JSlider(10,20);
sizeSlider.setMajorTickSpacing(1);
sizeSlider.setMinorTickSpacing(1);
sizeSlider.setPaintLabels(true);
sizeSlider.setPaintTicks(true);
sizeBox.add(sizeSlider);
//Cancel And Ok 创建并绑定对应盒子
cancelButton = new Button("Cancel");
okButton = new Button("Ok");
cancelButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
setVisible(false);
}
});
okButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
setName(inputText.getText());
setFontSize(sizeSlider.getValue());
setVisible(false);
}
});
buttonBox.add(cancelButton);
buttonBox.add(okButton);
//overall绑定与子盒子绑定
add(overallBox);
overallBox.add(new Label("The quick brown box.."));
overallBox.add(familyBox);
overallBox.add(fontListBox);
overallBox.add(weightBox);
overallBox.add(slantBox);
overallBox.add(sizeBox);
overallBox.add(buttonBox);
//设置对话框的状态
setBounds(100,100,300,300);
setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
public int getWeight() {
return weight;
}
public int getSlant() {
return slant;
}
public void setWeight(int weight) {
this.weight = weight;
}
public void setSlant(int slant) {
this.slant = slant;
}
public int getFontSize() {
return fontSize;
}
public void setFontSize(int fontSize) {
this.fontSize = fontSize;
}
}
FontChooser类
package behaviorTypePattern.mediatorPattern.GUI;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class FontChooser extends JFrame {
JButton button;
FontChooserDialog fontChooserDialog;
JTextArea textArea;
public FontChooser() throws HeadlessException {
//采用流式布局
setLayout(new FlowLayout());
//创建文本区组件并放在最上边
textArea = new JTextArea("点按钮改变文本风格");
textArea.setColumns(20);
textArea.setRows(2);
add(textArea,BorderLayout.NORTH);
//创建自定义对话框
fontChooserDialog = new FontChooserDialog(this,"dialog");
fontChooserDialog.setModal(true);
//创建按钮并放在中间
button = new JButton("font choose");
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
fontChooserDialog.setVisible(true);//当点击按钮时弹出对话框
Font font = getFontShape();//对话框结束时调用getFontShape()得到Font
textArea.setFont(font);//并为文本区设置Font
}
});
add(button,BorderLayout.CENTER);
//设置Frame的状态
setVisible(true);
setBounds(100,100,300,300);
}
/**
* 从FontChooserDialog得到Font的参数name,style,size,新建并返回一个Font
* @return font
*/
public Font getFontShape() {
String name = fontChooserDialog.getName();
int style = fontChooserDialog.getWeight() + fontChooserDialog.getSlant();
int size = fontChooserDialog.getFontSize();
Font font = new Font(name,style,size);
return font;
}
public static void main(String[] args) {
FontChooser fontChooser = new FontChooser();
}
}
运行代码
public static void main(String[] args) {
FontChooser fontChooser = new FontChooser();
}
模式探讨
在我们大体实现了这个功能之后,我们来探讨背后的设计模式——中介者模式和观察者模式。
虽然上面的代码看上去与Gof原书的代码实现不太相同,主要是语言【Java和C++】,另外Gof原书代码并没有写完整,但模式思想还是基于Gof。
中介者模式
我们自定义一个组件常用组合关系,一个父组件组合多个子组件。当子组件发生交互和通信时,这个时候我们可以**把父组件看作中介者。**如图,我们可以发现 FontChooser 组合了 FontChooserDialog 和 JTextArea 。当弹出的对话框被关闭时,FontChooser 还会调用 getFontStyle() 方法获得 Font,返回给 JTextArea,此时FontChooser就充当 FontChooserDialog 和 JTextArea的中介。
观察者模式
请仔细看看下面这段绑定监听事件的代码。
button = new JButton("font choose");
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
fontChooserDialog.setVisible(true);//当点击按钮时弹出对话框
Font font = getFontShape();//对话框结束时调用getFontShape()得到Font
textArea.setFont(font);//并为文本区设置Font
}
});
这段代码背后隐藏着一个观察者模式【点击可了解】**。**我们可以从addActionListener()方法和ActionListener()接口看出来的。
经典的观察者模式的UML图如上图所示。其中抽象类 Subject 作为被观察者,接口 Observer作为观察者。Subject 和 Observer 为 多对一 的组合关系,从而达到对象之间相互组合的目的:当 Subject发生变化时,会调用notify()通知它注册的观察者,调用它的update()方法更新变化的数据。update()方法执行的是具体的响应逻辑。
当然,下面我们来看看 JButton 背后的点击事件原理。
请看基于JDK8源码分析的下图。
由UML图知道,
**ActionListener是一个接口,**需要实现抽象方法 actionPerformed。
**AbstractButton 是 JButton的父类。**其中有三个重要的方法
-
addActionListener(ActionListener l)添加 ActionListener
-
removeActionListener(ActionListener l)删除 ActionListener
-
fireActionPerformed(ActionEvent event)通知所有注册的 ActionListener。
ActionEvent 是动作事件。ActionEvent 作为一个对象由 AbstractButton 传递给 ActionListener, 也可以理解为事件源传递动作的事件给监听器对象。
ActionEvent包含事件发生的三个参数 when 时间、modifiers 用于确定鼠标或按键的标识符、 actionCommand 事件源的细节信息。帮助我们可以更方便排错。
总结
在我们理清了 JButton背后隐藏的观察者模式之后,我们可以自然地画出这张逻辑图。
参考:最后一张图灵感的来源。