观察者模式高级应用--自定义Swing事件监听器

先抛出问题:
问题描述
因为之前看到的实现很复杂,通过先将面板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中设断点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值