通过Java Swing看透MVC设计模式

guojing 2011-06-09

一个好的用户界面(GUI)的设计通常可以在现实世界找到相应的表现。例如,如果在您的面前摆放着一个类似于电脑键盘按键的一个简单的按钮,然而就是这么简单的一个按钮,我们就可以看出一个GUI设计的规则,它由两个主要的部分构成,一部分使得它具有了按钮应该具有的动作特性,例如可以被按下。另外一部分则负责它的表现,例如这个按钮是代表了A还是B。

看清楚这两点你就发现了一个很强大的设计方法,这种方法鼓励重用reuse,而不是重新设计redesign。你发现按钮都有相同的机理,你只要在按钮的顶上喷上不同的字母便能制造出“不同”的按钮,而不用为了每个按钮而重新设计一份图纸。这大大减轻了设计工作的时间和难度。

这当然很不错,但是或许您又开始疑惑这和java基础类JFC(JavaFoundationClass)中的用户界面设计部分(Swing)又有什么关系呢?好的,我来告诉你。

MVC设计模式把一个软件组件区分为三个不同的部分,model,view,controller。

View代表了管理model所含有的数据的一个视觉上的呈现。一个Model可以有一个以上的View,但是Swing中却很少有这样的情况。

使用键盘上的按钮的例子来说明一下:Model就是按钮的整个机械装置,View/Controller就是按钮的表面部分。

用Button的例子详细说明

我们从model来开始。

一个按钮的model所应该具备的行为由一个接口ButtonModel来完成。一个按钮model实例封装了其内部的状态,并且定义了按钮的行为。它的所有方法可以分为四类:

1、查询内部状态

2、操作内部状态

3、添加和删除事件监听器

4、发生事件

其他用户界面组件有他们各自的与组件相关的MODEL都提供这四个类方法.

View&Controller

1.绘制Paint

2.返回几何类型的信息

3.处理AWT事件

其他用户界面组件有他们自己的组件相关的View/Controller,但是他们都提供上述三类方法.

程序员通常并不会直接和model以及view/controller打交道,他们通常隐藏于那些继承自java.awt.Component的组件里面了,这些组件就像胶水一样把MVC三者合三为一。也正是由于这些继承的组件对象,一个程序员可以很方便的混合使用Swing组件和AWT组件,然后,我们知道,Swing组件有很多都是直接继承自相应的AWT组件,它能提供比AWT组件更加方便易用的功能,所以通常情况下,我们没有必要混合使用两者。

实例

现在我们已经明白了Java类与MVC各个部分的对应关系,我们可以更加深入一点去分析问题了。下面我们将要讲述一个小型的使用MVC模式开发的例子。因为JFC十分的复杂,我只能把我的例子局限于一个用户界面组件里面(Button)

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();
}

ButtonModel类

ButtonModel维护着三种类型的状态信息:是否被按下(pressed),是否“武装上了”(armed),是否被选择(selected)。它们都是boolean类型的值。

一个按钮被按下(pressed)是指当鼠标在按钮上面的时候,按下鼠标但是还没有松开鼠标按钮的状态,及时用户此时把鼠标拖拽到按钮的外面也没有改变这种状态。

一个按钮是否“武装了”(armed)是指按钮被按下,并且鼠标还在按钮的上面。

一些按钮还可能被选择(selected),这种状态通过重复的点击按钮取得true或者false的值。

下面的代码是状态pressed的一个缺省的实现。状态armed以及selected实现的代码与之类似。ButtonModel类应该被继承,这样可以覆盖缺省的状态定义,实现有个性的按钮。

private boolean boolPressed = false;
public boolean isPressed(){
  return boolPressed;
}
public void setPressed(boolean boolPressed){
  this.boolPressed = boolPressed;
  fireChangeEvent(new ChangeEvent(button));
}

按钮的模型buttonmodel还负责通知其他对象(事件监听器)他们所感兴趣的事件.从下面的代码中我们可以看出当按钮的状态发生改变的时候就会发出一个ChangeEvent.下面就是代码:

private Vector vectorChangeListeners=new Vector();
public void addChangeListener(ChangeListener changelistener){
  vectorChangeListeners.addElement(changelistener);
}
public void removeChangeListener(ChangeListener changelistener){
  vectorChangeListeners.removeElemet(changelistener);
}
protected void fireChangeEvent(ChangeEvent changeevent){
  Enumeration enumeration=vectorChangeListers.elements();
  while(enumeration.hasMoreElements()){
  	ChangeListener changelistener=(ChangeListener)enumeration.nextelement();
	  changelistener.stateChanged(changeevent);
  }
}

ButtonUI类  

按钮的view/controller是负责构建表示层的。缺省情况下它仅仅是用背景色画一个矩形而已,他们的子类继承了他们并且覆盖了绘制的方法,使得按钮可以有许多不同的表现,例如MOTIF,Windows95,Java样式等等。

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);
}

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);
}

View/Controller实际上就是一些方法。他们不维护任何自己的状态信息。因此,许多按钮的实例可以共享一个ButtonUI实例。ButtonUI是通过在方面的参数列表里面加上按钮的引用来区分各个不同的按钮。

同样,希望你能多花一些时间来看看ButtonUI类,然后咱们进入下一节。

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();
}

总结

我希望你能按照上面讲述的方法去做。如果不能,那么所有的努力都将白费。这个例子以及Swing用户界面组件的好处在于你不用去花时间去弄明白他们底层是如何设计实现的就可以很方便的使用他们了。他们都提供了缺省的model以及view/controller,然后,当你自己做组件的时候,你会发现上面的思想的强大之处。

(转载:http://java.chinaitlab.com/model/797087.html)

相关推荐