通过Java Swing看透MVC设计模式
一、引言
(一)GUI 设计思想的启发
在现实世界中,一个简单的电脑键盘按键(如按钮)就体现了图形用户界面(GUI)的设计规则。它由动作特性(如可被按下)和表现(如代表的字母)两部分构成。这种设计思想可应用于软件开发,例如 Model/View/Controller(MVC) 设计模式。MVC 设计模式鼓励代码重用,减轻设计工作的时间和难度。就像通过改变按钮表面的字母就能制造出“不同”的按钮,而无需重新设计整个按钮的机械结构。
(二)MVC 与 Java Swing 的关系
MVC 设计模式通常用于设计整个用户界面(GUI),而 Java Foundation Class(JFC)中的 Swing 组件将 MVC 设计模式应用于单个组件。例如表格(JTable)、树(JTree)、组合下拉列表框(JComboBox)等组件都有各自的 Model、View 和 Controller。这些部分可以独立改变,即使组件正在被使用,这使得开发 GUI 界面的工具包非常灵活。
二、MVC 设计模式概述
(一)Model(模型)
功能与特性
- Model 代表组件状态和底层行为,管理自身状态并处理所有对状态的操作。
- 它不知道使用自己的 View 和 Controller 是谁,系统维护其与 View 的关系。当 Model 发生改变时,系统负责通知相应的 View。
- 示例:在一个游戏角色的模型中,它包含角色的生命值、攻击力等状态信息,以及升级、受伤等操作这些状态的方法。
(二)View(视图)
功能与特性
- View 负责管理 Model 所含数据的视觉呈现。
- 一个 Model 可以有多个 View,但在 Swing 中这种情况较少。
- 示例:在一个数据可视化系统中,同一组数据可以通过柱状图、折线图等不同的 View 来展示给用户。
(三)Controller(控制器)
功能与特性
- Controller 管理 Model 和用户之间的交互控制,提供处理 Model 状态变化情况的方法。
- 示例:以游戏中的角色控制为例,Controller 接收用户的输入(如键盘按键、鼠标点击),并根据这些输入改变角色 Model 的状态(如移动、攻击)。
(四)MVC 在按钮中的示例
用键盘按钮类比,Model 就像按钮的整个机械装置,负责按钮的行为逻辑(如是否被按下、是否可用等状态的管理);View/Controller 则如同按钮的表面部分,负责按钮的外观显示以及接收用户对按钮的操作(如点击、鼠标悬悬停等)。
配图说明:此处建议插入一个简单的按钮示意图,标注出 Model、View/Controller 部分。
三、Button 组件中的 MVC 实现
(一)Button 类
关联 Model 和 View/Controller 的代码分析
Button类是 Model 和 View/Controller 之间的核心桥梁。每个按钮组件都与一个 Model 和一个 Controller 关联:Model 定义按钮行为,View/Controller 定义按钮表现。应用程序可随时改变这些关联。
public void setModel(ButtonModel buttonmodel) {
if (this.buttonmodel != null) {
this.buttonmodel.removeChangeListener(buttonchangelistener);
this.buttonmodel.removeActionListener(buttonactionlistener);
buttonchangelistener = null;
buttonactionlistener = null;
}
this.buttonmodel = buttonmodel;
if (this.buttonmodel != null) {
buttonchangelistener = new ButtonChangeListener();
buttonactionlistener = new ButtonActionListener();
this.buttonmodel.addChangeListener(buttonchangelistener);
this.buttonmodel.addActionListener(buttonactionlistener);
}
updateButton();
}
public void setUI(ButtonUI buttonui) {
if (this.buttonui != null) {
this.buttonui.uninstallUI(this);
}
this.buttonui = buttonui;
if (this.buttonui != null) {
this.buttonui.installUI(this);
}
updateButton();
}
public void updateButton() {
invalidate();
}- 上述代码中,
setModel方法用于设置按钮的 Model。当设置新的 Model 时,会先移除旧 Model 的相关监听器,然后添加新 Model 的监听器,并更新按钮状态。 setUI方法用于设置按钮的 View/Controller。类似地,会先卸载旧的 UI,再安装新的 UI 并更新按钮。updateButton方法则通过调用invalidate来触发按钮的重绘,确保按钮的显示与新的 Model 和 View/Controller 设置保持一致。
(二)ButtonModel 类
状态信息与事件通知代码分析
ButtonModel维护按钮的状态信息,如是否被按下(pressed)、是否处于武装状态(armed)、是否被选择(selected),这些都是布尔类型值。
private boolean boolPressed = false;
public boolean isPressed() {
return boolPressed;
}
public void setPressed(boolean boolPressed) {
this.boolPressed = boolPressed;
fireChangeEvent(new ChangeEvent(button));
}- 以上代码展示了
pressed状态的实现。isPressed方法用于查询按钮是否被按下,setPressed方法不仅设置按钮的按下状态,还会通过fireChangeEvent方法触发状态改变事件,通知其他监听器按钮状态发生了变化。 ButtonModel还负责通知其他对象(事件监听器)感兴趣的事件,通过维护一个监听器列表来实现。
private Vector vectorChangeListeners = new Vector();
public void addChangeListener(ChangeListener changelistener) {
vectorChangeListeners.addElement(changelistener);
}
public void removeChangeListener(ChangeListener changelistener) {
vectorChangeListeners.removeElement(changelistener);
}
protected void fireChangeEvent(ChangeEvent changeevent) {
Enumeration enumeration = vectorChangeListeners.elements();
while (enumeration.hasMoreElements()) {
ChangeListener changelistener = (ChangeListener) enumeration.nextElement();
changelistener.stateChanged(changeevent);
}
}addChangeListener和removeChangeListener方法用于添加和移除监听器。fireChangeEvent方法在状态改变时遍历监听器列表,调用每个监听器的stateChanged方法,通知它们按钮状态发生了改变。
配图说明:此处建议插入一个按钮状态变化的流程图,展示从用户操作到 Model 状态改变,再到通知监听器的过程。
(三)ButtonUI 类
构建表示层与事件处理代码分析
ButtonUI负责构建按钮的表示层,默认情况下仅用背景色画一个矩形。
public void update(Button button, Graphics graphics) {
}
public void paint(Button button, Graphics graphics) {
Dimension dimension = button.getSize();
Color color = button.getBackground();
graphics.setColor(color);
graphics.fillRect(0, 0, dimension.width, dimension.height);
}update方法可用于在按钮状态改变时进行一些预处理。paint方法则负责绘制按钮的外观,根据按钮的大小和背景色绘制一个矩形。ButtonUI不直接处理 AWT 事件,而是通过定制的事件监听器将低级的 AWT 事件翻译为高级的 Button 模型期望的语义事件。
private static ButtonUIListener buttonuilistener = null;
public void installUI(Button button) {
button.addMouseListener(buttonuilistener);
button.addMouseMotionListener(buttonuilistener);
button.addChangeListener(buttonuilistener);
}
public void uninstallUI(Button button) {
button.removeMouseListener(buttonuilistener);
button.removeMouseMotionListener(buttonuilistener);
button.removeChangeListener(buttonuilistener);
}installUI方法在按钮安装时添加相关事件监听器。uninstallUI方法在按钮卸载时移除这些监听器。
配图说明:此处建议插入一个按钮绘制过程的示意图,展示从 ButtonUI 的 paint 方法到实际在屏幕上显示按钮的过程。
(四)ButtonUIListener 类
事件处理逻辑代码分析
ButtonUIListener帮助Button类将鼠标或键盘输入转换为对按钮模型的操作,实现了MouseListener、MouseMotionListener和ChangeListener接口。
public void mouseDragged(MouseEvent mouseevent) {
Button button = (Button) mouseevent.getSource();
ButtonModel buttonmodel = button.getModel();
if (buttonmodel.isPressed()) {
if (button.getUI().contains(button, mouseevent.getPoint())) {
buttonmodel.setArmed(true);
} else {
buttonmodel.setArmed(false);
}
}
}
public void mousePressed(MouseEvent mouseevent) {
Button button = (Button) mouseevent.getSource();
ButtonModel buttonmodel = button.getModel();
buttonmodel.setPressed(true);
buttonmodel.setArmed(true);
}
public void mouseReleased(MouseEvent mouseevent) {
Button button = (Button) mouseevent.getSource();
ButtonModel buttonmodel = button.getModel();
buttonmodel.setPressed(false);
buttonmodel.setArmed(false);
}
public void stateChanged(ChangeEvent changeevent) {
Button button = (Button) changeevent.getSource();
button.repaint();
}mouseDragged方法:在鼠标拖动时,根据按钮 Model 的按下状态和鼠标位置更新按钮的“武装”状态。mousePressed方法:在鼠标按下时设置按钮的按下和“武装”状态。mouseReleased方法:在鼠标释放时重置按钮的按下和“武装”状态。stateChanged方法:在按钮状态改变时(如通过代码设置按钮状态),调用按钮的repaint方法重绘按钮,以反映最新状态。
四、总结
通过对 Java Swing 中 Button 组件的 MVC 设计模式分析,我们看到了 MVC 在单个组件中的实现细节。在实际使用中,我们无需深入了解其底层实现即可方便地使用 Swing 组件,因为它们提供了默认的 Model、View 和 Controller。
然而,当我们自己开发组件时,MVC 思想的强大之处就会显现出来。它能帮助我们构建出结构清晰、易于维护和扩展的软件系统。这种设计模式使得组件的各个部分职责分明:
- Model 专注于数据和状态管理;
- View 专注于显示;
- Controller 专注于用户交互控制。
它们之间的低耦合性保证了系统的灵活性和可维护性,为开发高质量的 GUI 应用提供了坚实的基础。
说明:本文基于 Java Swing 组件架构分析。Swing 作为 Java SE 的标准 GUI 工具包,虽在新技术栈(如 JavaFX 或 Web 前端)面前被视为传统技术,但其 MVC 实现机制依然稳定且适用于维护现有系统或学习设计模式。代码示例中的部分类(如 Vector)反映了早期 Java 版本风格,在现代开发中建议使用泛型集合替代。 版权声明:本文为原创文章,版权归 戴老师的博客 所有,转载请联系博主获得授权。
本文地址:https://1diff.fun/archives/tong-guo-java-swing-kan-tou-mvc-she-ji-mo-shi.html
如果对本文有什么问题或疑问都可以在评论区留言,我看到后会尽量解答。