可擴展的Java應用程序開發模式
現在很多JAVA書籍,在介紹SWING的事件模型時,為了使JAVA初學者容易理解和上手,通常是在主類(自己的JFrame子類或JPanel)中做所有事情:要么由主類作為監聽器,在接口的方法中通過判定事件源來決定具體做何事;要么在主類中使用內隱類來實現監聽器,(因為內隱類可以方便的訪問主類中的成員變量)。而很多JAVA初學者在往后的編程中也采用這種模式(至少我以前是這樣的)。其實,這種做法雖然簡單,卻缺乏可讀性、可擴展性和可維護性。之前我看到了一篇關于這個問題的文章(來自于《JavaPro》),現整理出來與大家分享。
下面我將用一個簡單的例子來說明如何使用模式來解決這個問題。
現在,假設我們要編寫一個使用幾個單選按鈕從一個長列表中選擇不同分組的程序,就象下圖中看到的。單擊Female單選按鈕時,只有女孩的名字被顯示;當你點擊Male按鈕時,程序顯示男孩的名字。
這個程序最簡單的版本是在主類中做每件事情,主類包含一些持有孩子姓名的數組和向量(Vector),一個另一個最簡單的方法是為每個孩子創建一個對象,每個對象有一個象isFemal()這樣的方法來幫助我們確定要顯示的是男的還是女的。在程序中我們把每個孩子的類稱為Swimmer:
下一步我們要做的是決定整個列表中的哪些孩子會被顯示。我們可以在主用戶接口類中做到這點,但是最好的是在一個叫做Swimmers的集合類中來做,這個類有一個getList()方法,它帶有布爾型的參數來決定是男孩還是女孩:
我們又提供了一個不帶參數的多態getList()方法來返回整個列表。
現在讓我們進入程序中的第一個重要的部分,為了顯示這些孩子的姓名,我們需要為三個單選按鈕都添加一個事件監聽器,使得每個都能顯示出正確的孩子們:
盡管這個方法在簡單、小型的情況下工作得很好,但是它沒有良好的可擴展性。如果你有10個按鈕,擴展actionPerformed()方法來測試每個按鈕和調用某些對應的操作,會使得可讀性非常差。相反,我們最好在包含用戶接口的類中去掉所有的判定。
做到這點的一個途徑是使用Command模式。當我們使用這個模式的時候,需要我們創立一個Command接口:
我們將三個單選按鈕擴展成特定的帶有Command接口的類,我們就可以把對命令的執行從JFrame類轉移到每個按鈕的類。同時,我們創建一個叫做SexButton的基類,并將ActionListener代碼移到基類中,這樣就不需要分別在每個按鈕中添加事件監聽器:
注意這個類必須被擴展以使得它有用,因為我們沒有具體化execute()方法。這個基本的抽象按鈕類只是我們從中導出的具體類的一個模板,這實際也是模板設計模式的一個簡單例子。
現在,來看一下我們完成的工作。所有的按鈕都成為了Command按鈕了,actionPerformed()方法也簡化成下面這樣簡單的方法:
正如你看到的,這更加簡單而且完全是可擴展的。
但我們還有要做的事情。現在我們寫了三個單選按鈕類,它們知道如何載入一個列表框。在我們匆忙地想要從主類之外得到消息時,我們要求每個按鈕都知道列表框。如果我們想要改成一個不同的顯示方式,就必須修改這三個類。
如果我們在按鈕和列表之間創建一個mediator(調停者)類就會好得多,因為它們彼此不需要知道對方。Mediator(調停者)設計模式能夠做到這一點。當一個按鈕被點擊時,我們創建一個調停者類載入列表。這樣所有按鈕只知道調停者,哪個列表被載入也只有調停者才知道,當我們想要改成一個不同的顯示方式就只需修改Mediator:
這樣我們的基類變成這樣:
而FemalButton則變成這樣:
你看,我們使用Mediator來分離按鈕和列表,使它們互相不知道對方。
除了可以使用命令、調停者、模板模式改進這個不到100行的Java代碼的簡單程序外,還可以使用觀察者(Observer)模式。這個程序使用JListData將數據傳給列表并從列表中獲得數據,并用LawList觀察JListData的變化。
下面我將用一個簡單的例子來說明如何使用模式來解決這個問題。
現在,假設我們要編寫一個使用幾個單選按鈕從一個長列表中選擇不同分組的程序,就象下圖中看到的。單擊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(); } } |
