最强案例,【中介者模式】在GUI界面组件中的使用

目录

题目

最终效果图

理解中介者模式的核心思想

思路分析

代码

项目目录结构

Mediator(中介者)

Component(组件)

Component(组件)的实现类

MainFrame(主窗口界面)


题目

某软件公司欲开发一套CRM系统,其中包含一个客户信息管理模块,所设计的“客户信息管理窗口”界面效果图如图所示。组件之间的交互关系如下:

(1) 当用户单击“增加”按钮、“删除”按钮、“修改”按钮或“查询”按钮时,界面左侧的“客户选择组合框”、“客户列表”以及界面中的文本框将产生响应。

(2) 当用户通过“客户选择组合框”选中某个客户姓名时,“客户列表”和文本框将产生响应。

(3) 当用户通过“客户列表”选中某个客户姓名时,“客户选择组合框”和文本框将产生响应。

请使用中介模式实现该系统,并补充程序中相应Java代码。

最终效果图

理解中介者模式的核心思想

按照我的理解和思路来总结一下。

以即时聊天为例子,A用户给B用户发消息,并不是A用户和B用户之间建立了点对点的连接。假如两个聊天用户之间都建立一对一、点对点的连接,那么要建立的连接就非常多了,就成为了个复杂网状结构。

实际上,A用户和B用户都与一台服务器建立了连接,A用户发消息给B用户,消息会经由服务器中转来发送。这台服务器就充当了中介者的角色,它持有了所有的用户连接。由于它的存在使得原先的网状结构变成了一(服务器)对多(多个连接)的发散的星状结构。

思路分析

先抛开代码如何实现,抛开界面如何绘制。先来宏观角度来想想。

在上述UI界面中,我们不妨定义 "组件"(component)的概念来类比上述的 "用户"。根据题意,我们可以得出以下几个组件:

Component 我们可以定义为接口(为什么不是抽象类?因为组件还需要集成Swing的组件类,而Java只能单继承),里面有两个方法:

change():当前组件发生了改变,作为事件源

update() :当前组件作为监听者,监听到了事件,从而更新来做出响应

而中介者(Mediator),必定持有所有的 Component,而且持有所有的 User,因此中介者(Mediator)需要是全局唯一的,是单例的。当某个 Component 作为事件源调用了 change() 方法,中介者(Mediator)统一做通知分发:调用除了事件源以外的其他组件的 update() 方法。

代码

项目目录结构

Mediator(中介者)

  1. 饿汉单例模式
  2. 使用 List 存储所有的 Component(使用接口,利用多态)
  3. 作为 User 的数据源,用 Map 存储所有的 User,key 是 username
  4. componentChanged(Component component, User user) 是给作为事件源组件调用的来通知更新的,第1个参数是作为事件源的组件,第2个参数是更新的数据

还挺简洁的吧。。。

package demo;

import demo.component.Component;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 中介者
 * @author passerbyYSQ
 * @create 2021-06-03 0:01
 */
public class Mediator {
    // 所有的组件
    private List<Component> list = new ArrayList<>();
    private Map<String, User> users = new HashMap<>();
    // 中介者的单例
    private static final Mediator INSTANCE;

    static {
        INSTANCE = new Mediator(); // 饿汉
    }

    private Mediator() {}

    public static Mediator getInstance() {
        return INSTANCE;
    }

    public void addComponent(Component component) {
        list.add(component);
    }

    public void putUser(User user) {
        users.put(user.getUsername(), user);
    }

    public void removeUser(String username) {
        users.remove(username);
    }

    public String[] getUsernameList() {
        return users.keySet().toArray(new String[0]);
    }

    public User getUser(String username) {
        return users.getOrDefault(username, new User(username));
    }

    public void componentChanged(Component component, User user) {
        for (Component com : list) {
            if (!com.equals(component)) { // component是事件源
                com.update(user);
            }
        }
    }

}

Component(组件)

  1. Component 肯定需要持有 Meditor (不然怎么建立双向联系?)Meditor 是全局单例,可以在任何地方方便的获得,也可以在实现类中以构造方法参数的形式传入。但为了方便,直接在接口中定义也是允许的。那么在实现类中可以直接使用
  2. 同时利用 jdk 1.8 的新特性在接口中就为 change() 方法提供了默认实现。
  3. update() 方法是根据 User 数据,来对当前组件进行界面上的更新。各个组件的界面更新都不一样,所以需要留到子类来实现。
package demo.component;

import demo.Mediator;
import demo.User;

/**
 * @author passerbyYSQ
 * @create 2021-06-02 23:57
 */
public interface Component {

    Mediator mediator =  Mediator.getInstance();

    void update(User user);

    default void change(User user) {
        mediator.componentChanged(this, user);
    }
}

Component(组件)的实现类

1. SearchTextField(搜索文本框)

  1. 直接在空参构造里面就将当前组件对象注册到中介者(Mediator)中。这一个操作可以放到外面去做。但是组件本身已经持有了Mediator,为了方便我们可以放到构造方法里面。
  2. update():通过调用父类JTextField 来更新界面

其他组件依葫芦画瓢...

/**
 * @author passerbyYSQ
 * @create 2021-06-03 0:00
 */
public class SearchTextField extends JTextField implements Component {

    public SearchTextField() {
        mediator.addComponent(this);
    }

    @Override
    public void update(User user) {
        setText(user.getUsername());
    }

}

2. UsernameCombBox(用户名下拉选择框)

  1. Mediator 持有了 User 数据源。而新增 User 之后,下拉选择框中必然多一条记录。因此在做 "选中" 操作之前,我们先拿到数据源,重置下拉选择框的数据。然后再去找与 User 对应的选项,将其选中
package demo.component;

import demo.User;

import javax.swing.*;

/**
 * 下拉选择框
 * @author passerbyYSQ
 * @create 2021-06-03 0:15
 */
public class UsernameCombBox extends JComboBox<String> implements Component {

    public UsernameCombBox() {
        mediator.addComponent(this);
    }

    @Override
    public void update(User user) {
        // 数据源可能有更新,先重置数据源
        setModel(new DefaultComboBoxModel<>(mediator.getUsernameList()));

        boolean isFound = false;
        ComboBoxModel<String> model = getModel();
        for (int i = 0; i < model.getSize(); i++) {
            String item = model.getElementAt(i);
            if (item.equals(user.getUsername())) {
                setSelectedItem(item);
                //setSelectedIndex(i);
                isFound = true;
            }
        }
        if (!isFound) { // 没有任何匹配项,取消选中
            setSelectedItem(null);
        }

    }

}

3. UsernameList(用户名列表)

这个跟 UsernameCombBox 很像,不多说了。直接贴代码

package demo.component;

import demo.User;

import javax.swing.*;

/**
 * @author passerbyYSQ
 * @create 2021-06-03 0:20
 */
public class UsernameList extends JList<String> implements Component {

    public UsernameList() {
        setAutoscrolls(true);
        mediator.addComponent(this);
    }

    @Override
    public void update(User user) {
        // 可能有新增数据
        setModel(new DefaultListModel(mediator.getUsernameList()));

        boolean isFound = false;
        ListModel<String> usernameList = getModel();
        for (int i = 0; i < usernameList.getSize(); i++) {
            String item = usernameList.getElementAt(i);
            if (item.equals(user.getUsername())) {
                setSelectedIndex(i);
                isFound = true;
            }
        }
        if (!isFound) {
            clearSelection();
        }
    }

    public static class DefaultListModel extends AbstractListModel<String> {
        private String[] model;

        public DefaultListModel(String[] model) {
            this.model = model;
        }

        @Override
        public int getSize() {
            return model.length;
        }

        @Override
        public String getElementAt(int index) {
            return model[index];
        }
    }
}

4. UserDetailPanel(用户详细信息面板)

这是一个复合组件,先抛开界面怎么画,剩下的麻烦的点有两个:

  1. 界面更新比较繁琐
  2. "增加 & 修改" 按钮的点击事件,"删除" 按钮的点击事件。基于前面的封装,把思路屡屡会发现。其实就两步:(1)先更新数据源;(2)再调用 change() 方法做事件分发,提供新的 User数据,让其他组件做出界面改变
package demo.component;

import demo.User;

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Enumeration;

/**
 * @author passerbyYSQ
 * @create 2021-06-03 1:24
 */
public class UserDetailPanel extends JPanel implements Component {

    private JTextField usernameText;
    private JTextField phoneText;
    private ButtonGroup genderGroup;


    public UserDetailPanel() {
        setBounds(204, 144, 335, 263);
        setLayout(null);

        JLabel usernameLabel = new JLabel("用户名");
        usernameLabel.setBounds(24, 16, 45, 24);
        add(usernameLabel);

        usernameText = new JTextField();
        usernameText.setBounds(94, 16, 161, 24);
        add(usernameText);
        usernameText.setColumns(10);

        JLabel phoneLabel = new JLabel("手机");
        phoneLabel.setBounds(39, 124, 30, 18);
        add(phoneLabel);

        phoneText = new JTextField();
        phoneText.setBounds(94, 121, 161, 24);
        add(phoneText);
        phoneText.setColumns(10);

        JLabel genderLabel = new JLabel("性别");
        genderLabel.setBounds(34, 64, 30, 24);
        add(genderLabel);

        JRadioButton maleRadio = new JRadioButton("男");
        maleRadio.setBounds(102, 64, 43, 27);
        add(maleRadio);

        JRadioButton femaleRadio = new JRadioButton("女");
        femaleRadio.setBounds(170, 64, 43, 27);
        add(femaleRadio);

        // 加入按钮组就能单选
        genderGroup = new ButtonGroup();
        genderGroup.add(maleRadio);
        genderGroup.add(femaleRadio);

        JButton addOrUpdateBtn = new JButton("增加 & 修改");
        addOrUpdateBtn.setBounds(70, 188, 106, 27);
        add(addOrUpdateBtn);
        addOrUpdateBtn.addActionListener(e -> {
            User user = getUser();
            if (user.getUsername().isEmpty()) {
                return;
            }
            mediator.putUser(user); // 新增或修改
            change(user); // UserDetailPanel 是事件源
        });

        JButton deleteBtn = new JButton("删除");
        deleteBtn.setBounds(206, 188, 63, 27);
        deleteBtn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                User user = getUser();
                if (user.getUsername().isEmpty()) {
                    return;
                }
                mediator.removeUser(user.getUsername());
                change(new User());
            }
        });
        add(deleteBtn);

        mediator.addComponent(this); // 不要忘了!!!
    }

    @Override
    public void update(User user) {
        selectGender(user);
        usernameText.setText(user.getUsername());
        phoneText.setText(user.getPhone());
    }

    // 选中对应的性别
    public void selectGender(User user) {
        // 删除的时候需要,但未生效
        clearSelectedGender();

        Enumeration<AbstractButton> radios = genderGroup.getElements();
        while (radios.hasMoreElements()) {
            AbstractButton radio = radios.nextElement();
            if (radio.getText().equals(user.getGender())) {
                radio.setSelected(true);
            }
        }
    }

    // 获取选中的性别
    public String getSelectedGender() {
        Enumeration<AbstractButton> radios = genderGroup.getElements();
        while (radios.hasMoreElements()) {
            AbstractButton radio = radios.nextElement();
            if (radio.isSelected()) {
                return radio.getText();
            }
        }
        return null;
    }

    // 清除单选
    public void clearSelectedGender() {
        Enumeration<AbstractButton> radios = genderGroup.getElements();
        while (radios.hasMoreElements()) {
            AbstractButton radio = radios.nextElement();
            radio.setSelected(false);
        }
    }

    public User getUser() {
        String username = usernameText.getText();
        String gender = getSelectedGender();
        String phone = phoneText.getText();
        return new User(username, gender, phone);
    }

}

MainFrame(主窗口界面)

核心的点就是3个点击事件

  1. 搜索按钮的点击事件,而事件源是搜索输入框(SearchTextField)
  2. 用户名列表的某一项的选中事件
  3. 用户名下拉选择框的选中事件

此处做总结,实现见代码。思路理清,代码就不难了

另外,再来说一个最繁琐的问题:如何画界面???

写代码我是用 idea 写的,而画界面我是用 Eclipse 来画的,因为 Eclipse 有一款免费的Java GUI构建插件 WindowBuidler,可以比较方便地通过可视化界面的方式画界面。我在 Eclipse 将界面画好,变量名改好(强迫症患者)。然后再粘贴到 idea 来改。

package demo;

import demo.component.SearchTextField;
import demo.component.UserDetailPanel;
import demo.component.UsernameCombBox;
import demo.component.UsernameList;

import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

/**
 * @author passerbyYSQ
 * @create 2021年6月2日 下午10:23:45
 */
public class MainFrame extends JFrame {

    private Mediator mediator = Mediator.getInstance();


    /**
     * Launch the application.
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
            try {
                MainFrame frame = new MainFrame();
                frame.setVisible(true);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }

    /**
     * Create the frame.
     */
    public MainFrame() {

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(100, 100, 627, 455);
        JPanel contentPane = new JPanel();
        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
        setContentPane(contentPane);
        contentPane.setLayout(null);

        JLabel titleLabel = new JLabel("用户信息管理");
        titleLabel.setFont(new Font("宋体", Font.PLAIN, 26));
        titleLabel.setBounds(226, 13, 170, 48);
        contentPane.add(titleLabel);

        JLabel keywordLabel = new JLabel("关键词");
        keywordLabel.setBounds(226, 96, 57, 26);
        contentPane.add(keywordLabel);

        SearchTextField keywordText = new SearchTextField(); // 搜索框
        keywordText.setToolTipText("请输入用户名");
        keywordText.setBounds(284, 96, 182, 26);
        contentPane.add(keywordText);
        keywordText.setColumns(10);

        JButton searchBtn = new JButton("查询");
        searchBtn.setBounds(494, 96, 63, 27);
        searchBtn.addActionListener(new AbstractAction() { // 搜索按钮的点击事件
            @Override
            public void actionPerformed(ActionEvent e) {
                String keyword = keywordText.getText();
                if (!keyword.isEmpty()) {
                    keywordText.change(mediator.getUser(keyword));
                }
            }
        });
        contentPane.add(searchBtn);

        JScrollPane scrollPane = new JScrollPane();
        scrollPane.setBounds(33, 165, 126, 208);
        contentPane.add(scrollPane);

        UsernameList usernameList = new UsernameList();
        scrollPane.setViewportView(usernameList);
        usernameList.addMouseListener(new MouseAdapter() { // 鼠标点击事件
            @Override
            public void mouseClicked(MouseEvent e) {
                String username = usernameList.getSelectedValue();
                usernameList.change(mediator.getUser(username));
            }
        });

        UsernameCombBox usernameSelect = new UsernameCombBox();
        usernameSelect.setBounds(33, 96, 126, 26);
        usernameSelect.addActionListener(new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String username = (String) usernameSelect.getSelectedItem();
                usernameSelect.change(mediator.getUser(username));
            }
        });
        contentPane.add(usernameSelect);

        UserDetailPanel userDetailPanel = new UserDetailPanel();
        contentPane.add(userDetailPanel);

        setLocationRelativeTo(null);
        setResizable(false);
    }
}

最后,贴出最没有技术含量的一个类,User类

package demo;

/**
 * @author passerbyYSQ
 * @create 2021-06-02 23:58
 */
public class User {
    private String username;
    private String gender;
    private String phone;

    public User() {
    }

    public User(String username) {
        this.username = username;
    }

    public User(String username, String gender, String phone) {
        this.username = username;
        this.gender = gender;
        this.phone = phone;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值