先抛出问题:
因为之前看到的实现很复杂,通过先将面板B中数据发送到服务器,服务器校验完成后然后存储,然后通过ActiveMQ将消息发布到所有的客户端,告诉他们有了新的数据。terrible!
现在就看这个问题的简单实现需求:直接将填完的面板B已经填完的数据回传到面板A,这里就会用到观察者模式。
之前有学习过观察者模式,但是说实话与其一遍又一遍的看那些hello world教程去理解什么是观察者模式,还不如自己动手创新。最好的案例就是JDK中的所有事件触发器,问了两个人关于自定义事件触发器的写法,它们都说不能实现,说什么是JDK内置的硬件触发机制,我认为那就是bullshit。然后根据上面需求自己写一个这样的“事件触发器“呢?废话不多说,上菜:
package data;
/**
* 观察者:主题有新的动作时,观察者会得到通知并做出事件响应
*/
public class AutoUpdateTable extends JFrame implements ActionListener, DataChangeListener {
private JTable table;
private JPanel gridPanel;
private JScrollPane scrollPane;
private OwnTableModel tableModel;
private String[] headers = {"", "姓名", "性别", "电话", "地址", "现在公司"};
private JButton addUser;
public AutoUpdateTable(){
tableModel = new OwnTableModel(0, 10);
tableModel.setColumnIdentifiers(headers);
tableModel.addDataChangeListener(this);
gridPanel = new JPanel();
table = new JTable();
table.setModel(tableModel);
JTableHeader tableHeader = table.getTableHeader();
((DefaultTableCellRenderer)table.getTableHeader().getDefaultRenderer()).setHorizontalAlignment(JLabel.CENTER);
tableHeader.setBackground(Color.gray);
tableHeader.setForeground(Color.WHITE);
table.setGridColor(new Color(123, 124, 123));
scrollPane = new JScrollPane(table);
addUser = new JButton("新增");
addUser.setActionCommand("increase");
addUser.addActionListener(this);
this.add(scrollPane);
this.add(addUser, BorderLayout.SOUTH);
this.setSize(new Dimension(700, 600));
this.setLocation(400, 300);
//设置JFrame界面背景
// this.setContentPane(new ImagePanel(image));
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
}
@Override
public void actionPerformed(ActionEvent e) {
String command = e.getActionCommand();
if(command.equals("increase")){
new IncreaseDialog(AutoUpdateTable.this, "增加用户", false);
}
}
public static void main(String[] args) {
new AutoUpdateTable();
}
@Override
public void dataChanged(DataEvent e) {
Student student = e.getStudent();
System.out.println("数据已经进行了回传:" + student.toString());
//在这里进行表格更新操作
Vector v = new Vector();
v.add("");
v.add(student.getName());
v.add(student.getGender());
v.add(student.getTelephone());
v.add(student.getAddress());
tableModel.addRow(v);
tableModel.fireTableDataChanged();
}
}
我的这个DataChangeListener应该挂在谁身上呢?想了想,JTable数据更新主要依赖于DataModel,依赖于原始的DefaultTableModel就可以了,有新的数据来时通知它进行更新:
package data;
import javax.swing.table.DefaultTableModel;
/**
* 这里我比较懒,不想做什么额外的花哨的表格,也就没重写什么方法
* 当然这个DefaultTableModel还是不建议去重写的,因为它本身的逻辑已经很完整了
* 你的重写很有可能破坏JDK原始逻辑,导致数据表格不更新情况。
* @author crabime
*
*/
public class OwnTableModel extends DefaultTableModel {
public OwnTableModel(int column, int row){
super(column, row);
}
public void addDataChangeListener(DataChangeListener listener){
Observer.addDataChangeListener(listener);
}
}
有了一个JTable了后,那里面所有数据我觉得还是放到一个JavaBean中比较好,这里是我随便写了一个Student类,并且我还没有把它的所有属性写完整,因为它不是这个实例的重点:
package data;
public class Student {
private String name;
private char gender;
private String telephone;
private String address;
public Student(String name, char gender, String telephone, String address) {
super();
this.name = name;
this.gender = gender;
this.telephone = telephone;
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getGender() {
return gender;
}
public void setGender(char gender) {
this.gender = gender;
}
public String getTelephone() {
return telephone;
}
public void setTelephone(String telephone) {
this.telephone = telephone;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
当具体主题发出了事件通知时,它的消息传递都是通过一个Event来发送的,例如:ActionListener里面的ActionEvent,我们可以通过这个类来获取点击事件的事件源、命令等等,当然这里我需要将第二个用户注册面板的数据存放到这样一个DataEvent,然后传递到观察者那里:
package data;
public class DataEvent {
private Student student;
public DataEvent(Student student){
this.student = student;
}
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
}
我始终认为观察者与主题之间总是有一个第三者,他来维护者观察者与主题之间的关系,更确切的说是添加、删除、通知哪些观察者,这里我命名为Observer(有点歧义,但是不影响我的逻辑):
package data;
import java.util.ArrayList;
import java.util.List;
public class Observer {
private static List<DataChangeListener> list = new ArrayList<>();
private static Observer observer;
private Observer(){
}
public static Observer getInstance(){
if(observer == null){
observer = new Observer();
}
return observer;
}
public static void addDataChangeListener(DataChangeListener listener){
if(listener == null){
return;
}
list.add(listener);
}
/**
* 将这个事件通知所有注册者
* @param e:要发送的时间
*/
public static void notifyAllWatcher(DataEvent e){
for(DataChangeListener listener : list){
listener.dataChanged(e);
}
}
}
这里为什么要创建一个单例模式呢?因为最开始我没有写static List时,担心这样的一个集合会因为实例的创建而无法得到共享,那么有可能发生这样的事情:我AutoUpdateTable中明明添加了这样的一个监听器,调试时也看到的list集合将它添加进去了,但是通过在第二个面板数据填完准备告诉所有面板通知时,发现这个List集合是空的,这个问题在文章最后会出现,是一个比较有趣的事情。
有了这样的“第三者“后,我该去创建第二个注册面板了,这里也就是具体主题了:
package data;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class IncreaseDialog extends JDialog implements ActionListener {
/**
*
*/
private static final long serialVersionUID = 1L;
private JPanel root;
private JPanel leftPanel;
private JLabel username;
private JLabel gender;
private JLabel telephone;
private JLabel address;
private JLabel company;
private JTextField usernameField;
private JTextField genderField;
private JTextField telephoneField;
private JTextField addressField;
private JTextField companyField;
private JButton confirm;
private JButton cancel;
public IncreaseDialog(JFrame frame, String title, boolean modeless){
super(frame, title, modeless);
root = new JPanel();
leftPanel = new JPanel();
root.setLayout(new BoxLayout(root, BoxLayout.X_AXIS));
root.add(leftPanel);
createMainPanel();
confirm = new JButton("确定");
cancel = new JButton("取消");
confirm.addActionListener(this);
cancel.addActionListener(this);
confirm.setActionCommand("confirm");
cancel.setActionCommand("cancel");
JPanel panel = new JPanel();
panel.add(confirm);
panel.add(cancel);
this.add(root);
this.add(panel, BorderLayout.SOUTH);
this.setSize(new Dimension(500, 400));
this.setLocation(400, 300);
this.setVisible(true);
}
public void createMainPanel(){
username = new JLabel("用户名");
usernameField = new JTextField(10);
JPanel panel1 = new JPanel();
panel1.add(username);
panel1.add(usernameField);
gender = new JLabel("性别");
telephone = new JLabel("电话");
address = new JLabel("地址");
company = new JLabel("公司");
genderField = new JTextField(10);
telephoneField = new JTextField(10);
addressField = new JTextField(10);
companyField = new JTextField(10);
JPanel panel2 = new JPanel();
panel2.add(gender);
panel2.add(genderField);
JPanel panel3 = new JPanel();
panel3.add(telephone);
panel3.add(telephoneField);
JPanel panel4 = new JPanel();
panel4.add(address);
panel4.add(addressField);
JPanel panel5 = new JPanel();
panel5.add(company);
panel5.add(companyField);
leftPanel.add(panel1);
leftPanel.add(panel2);
leftPanel.add(panel3);
leftPanel.add(panel4);
leftPanel.add(panel5);
leftPanel.setLayout(new BoxLayout(leftPanel, BoxLayout.Y_AXIS));
root.add(leftPanel);
}
@Override
public void actionPerformed(ActionEvent e) {
String command = e.getActionCommand();
if(command.equals("confirm")){
Student result = (Student) checkInput();
//通过观察者模式将当前这组数据传送到前一个页面
publishEvent(result);
this.setVisible(false);
}else if(command.equals("cancel")){
this.getContentPane().setVisible(false);
}
}
public Object checkInput(){
String name = usernameField.getText();
char gender = genderField.getText().toCharArray()[0];
String telephone = telephoneField.getText();
String address = addressField.getText();
String company = companyField.getText();
Student student = new Student(name, gender, telephone, address);
return student;
}
public void publishEvent(Student student){
DataEvent event = new DataEvent(student);
Observer.notifyAllWatcher(event);
}
}
这里类里无非就是注意最后的事件发布代码,其它的没什么好说的。
最后不要忘了我们最重要的接口:抽象观察者
package data;
public interface DataChangeListener {
public void dataChanged(DataEvent e);
}
好了,这个例子到这里就结束了,但是下面还有一个思考题:
这里我通过AutoUpdateTable中confirm按钮点击事件去创建IncreaseDialog面板,但是如果我希望是完全两个独立的面板,也就是说在IncreaseDialog中增加一个main方法,然后先运行AutoUpdateTable、后运行IncreaseDialog,然后在在该dialog中添加一组数据看这组数据还能不能回传到AutoUpdateTable中?如果行,请告诉我方法;如果不行,为什么?这里给出小小hint,在Observer中设断点。