现在很多JAVA书籍,在介绍SWING的事件模型时,为了使JAVA初学者容易理解和上手,通常是在主类(自己的JFrame子类或JPanel)中做所有事情:要么由主类作为监听器,在接口的方法中通过判定事件源来决定具体做何事;要么在主类中使用内隐类来实现监听器,(因为内隐类可以方便的访问主类中的成员变量)。而很多JAVA初学者在往后的编程中也采用这种模式(至少我以前是这样的)。其实,这种做法虽然简单,却缺乏可读性、可扩展性和可维护性。之前我看到了一篇关于这个问题的文章(来自于《JavaPro》),现整理出来与大家分享。
下面我将用一个简单的例子来说明如何使用模式来解决这个问题。
现在,假设我们要编写一个使用几个单选按钮从一个长列表中选择不同分组的程序,就象下图中看到的。单击Female单选按钮时,只有女孩的名字被显示;当你点击Male按钮时,程序显示男孩的名字。
这个程序最简单的版本是在主类中做每件事情,主类包含一些持有孩子姓名的数组和向量(Vector),一个另一个最简单的方法是为每个孩子创建一个对象,每个对象有一个象isFemal()这样的方法来帮助我们确定要显示的是男的还是女的。在程序中我们把每个孩子的类称为Swimmer:
public class Swimmer{ private String name; private int age; private String club; private boolean female;
public Swimmer(String name,int age,String club,boolean female){ this.name=name; this.age=age; this.club=club; this.female=female; }
public boolean isFemale(){ return female; }
public String getName(){ return name; }
public int getAge(){ return age; }
public String getClub(){ return club; } } | 下一步我们要做的是决定整个列表中的哪些孩子会被显示。我们可以在主用户接口类中做到这点,但是最好的是在一个叫做Swimmers的集合类中来做,这个类有一个getList()方法,它带有布尔型的参数来决定是男孩还是女孩:
//get a vector of swimmers who are (female) public Vector getList(boolean female){ Vector v=new Vector(); for(int i=0;i<kids.size();i++){ Swimmer swm=(Swimmer)kids.elementAt(i); if(swm.isFemal()==female) v.add(swm); } return v; } | 我们又提供了一个不带参数的多态getList()方法来返回整个列表。
现在让我们进入程序中的第一个重要的部分,为了显示这些孩子的姓名,我们需要为三个单选按钮都添加一个事件监听器,使得每个都能显示出正确的孩子们:
public void actionPerformed(ActionEvent e){ //listen for button clicks and do the right thing Object obj=e.getSource(); if(obj==female) loadFemales(); if(obj==male) loadMales(); if(obj==both) loadBoth(); }
private void loadFemales(){ //display female swimmers Vector v=swimmers.getList(true); loadList(v); }
private void loadMales(){ //display male swimmers Vector v=swimmers.getList(false); loadList(v); } | 尽管这个方法在简单、小型的情况下工作得很好,但是它没有良好的可扩展性。如果你有10个按钮,扩展actionPerformed()方法来测试每个按钮和调用某些对应的操作,会使得可读性非常差。相反,我们最好在包含用户接口的类中去掉所有的判定。
做到这点的一个途径是使用Command模式。当我们使用这个模式的时候,需要我们创立一个Command接口:
//the Command interface public interface Command{ public void execute(); } | 我们将三个单选按钮扩展成特定的带有Command接口的类,我们就可以把对命令的执行从JFrame类转移到每个按钮的类。同时,我们创建一个叫做SexButton的基类,并将ActionListener代码移到基类中,这样就不需要分别在每个按钮中添加事件监听器:
//abstract radio button class public abstract class SexButton extends JRadioButton implements Command{ protected Swimmers simmers; //JawList is a subclass of JScrollPane contained a JList; protected JawList kidList;
public SexButton(String title,Swimmers sw, JawList klist,ActionListener al){ super(title); swimmers=sw; kidList=klist; addActionListener(al); }
//abstract execute method public abstract void execute(); } | 注意这个类必须被扩展以使得它有用,因为我们没有具体化execute()方法。这个基本的抽象按钮类只是我们从中导出的具体类的一个模板,这实际也是模板设计模式的一个简单例子。
//radio button to select female swimmers public class FemaleButton extends SexButton{ public execute(){ Vector v=swimmers.getList(true); loadList(v); }
private void loadList(Vector v){ kidList.clear(); for(int i=0;i<v.size();i++){ Swimmer swm=(Swimmer)v.elementAt(i); kidList.add(swm.getName()); } } } | 现在,来看一下我们完成的工作。所有的按钮都成为了Command按钮了,actionPerformed()方法也简化成下面这样简单的方法:
public void actionPerformed(ActionEvent e){ Command cmd=(Command)e.getSource(); cmd.execute(); } | 正如你看到的,这更加简单而且完全是可扩展的。
但我们还有要做的事情。现在我们写了三个单选按钮类,它们知道如何载入一个列表框。在我们匆忙地想要从主类之外得到消息时,我们要求每个按钮都知道列表框。如果我们想要改成一个不同的显示方式,就必须修改这三个类。
如果我们在按钮和列表之间创建一个mediator(调停者)类就会好得多,因为它们彼此不需要知道对方。Mediator(调停者)设计模式能够做到这一点。当一个按钮被点击时,我们创建一个调停者类载入列表。这样所有按钮只知道调停者,哪个列表被载入也只有调停者才知道,当我们想要改成一个不同的显示方式就只需修改Mediator:
public class Mediator{ private JawList kidList;
public Mediator(JawList klist){ kidList=klist; }
public void loadList(Vector v){ kidList.clear(); for(int i=0;i<v.size();i++){ Swimmer sw=(Swimmer)v.elementAt(i); kidList.add(sw.getName()); } } } | 这样我们的基类变成这样:
public abstract class SexButton extends JRadioButton implements Command{ protected Swimmers swimmers; protected Mediator med;
public SexButton(String title,Swimmers sw,Mediator md,ActionListener al){ super(title); swimmers=sw; med=md; addActionListener(al); }
public abstract void execute(); } | 而FemalButton则变成这样:
public class FemaleButton extends SexButton{ //use the mediator to load the list public void execute(){ Vector v=swimmers.getList(true); med.loadList(v); } } | 你看,我们使用Mediator来分离按钮和列表,使它们互相不知道对方。
除了可以使用命令、调停者、模板模式改进这个不到100行的Java代码的简单程序外,还可以使用观察者(Observer)模式。这个程序使用JListData将数据传给列表并从列表中获得数据,并用LawList观察JListData的变化。
public class JListData extends AbstractListModel{ private Vector data;
public JListData(){ data=new Vector(); }
public int getSize(){ return data.size(); }
public Object getElementAt(int index){ return data.elementAt(index); }
public void addElement(String s){ data.addElement(s); fireIntervalAdded(this,data.size()-1,data.size()); }
public void removeElement(String s){ data.removeElement(s); fireIntervalRemoved(this,0,data.size()); }
public void clear(){ int size=data.size(); data.clear(); fireIntervalRemoved(this,0,size); } }
public class JawList extends JScrollPane{ private JList listwindow; private JListData listContents;
public JawList(){ listContents=new JListData(); listwindow=new JList(listContents); getViewport().add(listwindow); }
public void add(String s){ listContents.addElement(s); }
public void remove(String s){ listCotents.removeElements(s); }
public void clear(){ listContents.clear(); } } |
|