假設這樣一種情況,我們的系統的界面使用Javax.swing包構建,界面的基礎是BaseVIEw,他是一個容器,當然他應當提供獲取控件元素的功能,比如得到按鈕,下拉框,表格等,當然僅僅是一個容器而已,而我們的界面的元素全部部署在JPanel上。
描述為:
一個界面就是一個BaseVIEw,他只包含一個JPanel,這個包含JPanel包含所有我們的Swing控件,例如JButton,JLable等等。
問題出現了:我們通常因為業務的需要完成一個界面的操作要自動跳轉到下一個界面,完成下一個界面又能跳回來(題外話:由於我們的操作是基於GUI的,所以往往能保存Session信息,而Web卻做不到),而這往往成為系統實現過程中效率低下的一個因素,我就見到我現在的系統中有人用600行代碼判斷上一個界面應該是哪一個來跳轉過來,因為很多界面都可以跳到當前界面。
當然有一種做法是,在下一個界面類中包含指向上一個界面的變量,我們說,這不方便,也增加了依賴性,這對軟件是不利的。
接下來,我給出我的解決方法,希望對采用這種界面結構的朋友有所裨益。
(以下全部用簡化模型來講述.)
1.簡單點,我們假設BaseVIEw繼承JWindow,當然可以是別的容器(依據你的實現),大概象這樣:
public abstract class BaseVIEw extends JWindow{
...
(實現一些取得界面控件,和界面信息的方法).
}
2.每個界面類都象這樣定義:
public class MyView extends BaseVIEw{
JPanel myPanel;
public void playoutPanel(){
JButton myButton = new JButton("OK");
myPanel.add(myButton);
......
(添加你需要的控件和布局到myPanel上)
}
}
3.假設有其他的界面OneView,TwoView,ThreeView處理完操作後都需要跳轉到myView,在myVIEw中的ok按鈕按下的時候需要回到原始界面。
原來臃腫的代碼需要在myView中添加一個變量BaseView anyView;用來存放轉來的那個界面anyView,賦值在三者中的跳轉代碼中引用myVIEw來設定.跳轉代碼象這樣:
public void jump(){
MyView myView = new MyVIEw();
myView.anyVIEw = this;
this.remove(this.xxPanel);
this.add(myVIEw.getPanel());
this.repaint();
}
看起來還不錯,雖然需要引用MyVIEw類,並調用他的變量和方法.但是跳轉回來卻不那麼容易,否則怎麼會用600行!
大概象這樣:(這已經是被我簡化的)
public void goBack(){
if(anyView instanceof OneVIEw){
anyView.remove(this.myVIEw);
OneView ov = (OneView)anyVIEw;
anyVIEw.add(ov.getPanel());
anyVIEw.repaint();
}
if(anyView instanceof TwoVIEw){
....
}
...
}
不經大量應用別的業務用例界面,這種編譯依賴性真不是什麼好事,更何況用了大量的低效的instanceof判斷和轉型操作.
為了優化這種情形,徹底解決這個問題,我想應該設計一個第三方類來消除這種依賴性,並且讓界面跳轉不要這麼費勁。這個第三方的類是這樣設計的:
在這個類中,必須有一個變量來保存某一個界面跳轉的路徑,如A->B->C.路徑一旦被保存,你就擁有了控制顯示任何一個界面的權利了。在這個鏈中,第一個位置的界面應該是這次跳轉的第一站,最後一個位置是當前站。這裡存在一個因果關系:只有跳轉了才可以跳回去。這樣使得我們可以用數組來保存這個路徑。現實中,跳轉的情形應該不會超過10次,所以我們把路徑長度設為10(當然你可以根據需要更改)。這個類的樣子大概象這樣:
class VIEwPath{
JPanel[] pnlPath = null; //跳轉的界面路徑,界面跳轉最大10個層次吧!!!
int index = 0; //路徑中的當前下標
BaseView bsView = null; //當前路徑所在的同一個VIEw
//在路徑中尋找目標的方法
public int find(JPanel pnl){ //該路徑下是否有某個Panel,有的話返回下標,沒有的話返回-1
if(bsVIEw==null) return -1; //沒有初始化,該路徑下沒有任何Panel
for(int i=0;i if(pnl==pnlPath[i]){
index = i;
return i; //如果找到了則返回位置,並且把當前位置設為目標位置
}
}
return -1; //沒有找到,返回-1
}
//構造函數
ViewPath(JPanel myPanel,BaseView myVIEw){
pnlPath = new JPanel[10]; //設置路徑最大長度為10
bsView = myView; //設置該路徑所屬的那個VIEw
pnlPath[0] = myPanel; //設立起始站
index = 0; //設立起始站索引
}
}
這樣一個類就完成了保存一次跳轉路徑的作用.(當然,是否應該在find方法中設立目標位置是否合適有待商榷)
那麼我們如何使用這樣一個路徑?
我們設立一個輔助類來完成這個工作,我們命名為VIEwJump,我們知道作為輔助的類最好是不要有實例,特別是象這樣的起接口作用的類,只提供靜態方法.它的框架象這樣:
public class VIEwJump{
private static ViewPath[] vIEwPath = null; //路徑池,系統多處使用,靜態但私有,因為供內部用
private VIEwJump(){} //私有構造方法,輔助類只提供靜態方法
private static int find(JPanel pnl); //尋找給定的Panel是否在已有路徑中,私有
private static int newPath(JPanel myPanel,BaseView myVIEw); //建立一個新路徑,私有
/**
* 每個類需要使用該輔助類時都需要第一步注冊自己,然後才能做其他操作
* 返回一個注冊碼id,輔助類需要使用這個注冊碼進行其他操作
*/
public static int registerPath(JPanel myPanel,BaseView myVIEw);
/**
* 設立下一個界面.
*/
public static void setNext(int id,JPanel aim);
/**
* 回到上一個界面
*/
public static void back(int id);
/**
* 回到第一個界面
*/
public static void backHome(int id);
/**
* 跳轉到下一個界面
*/
public static void jump(int id);
}
完成這樣一個類的代碼量並不多,一百多行,但是卻使得用戶完全脫離了處理不同界面的煩惱.稍後會把該類的源碼附上,值得一提的是,這個類的實現固然可以用到類似的實現當中,但是如果用戶的界面結構並不是如此搭建,你就需要更改參數類型了.如果能把這些抽象出來,得到一個抽象類或接口,參數用Object類型.用戶根據自己的需要去實現這些方法,豈不妙哉!
使用這個類,你可以簡便的多的完成諸如上面的任務:
OneVIEw中:
public void jump(){
MyView myView = new MyVIEw();
int id = VIEwJump.registerPath(this.xxPanel,this);
ViewJump.setNext(id,myVIEw.getPanel());
VIEwJump.jump(id);
}
MyVIEw中退回的部分:
protected void goBack(){
int id = VIEwJump.registerPath(this.myPanel,this);
VIEwJump.back(id);
}
天哪,這並不神奇,600行代碼僅僅用了兩行就實現了!
好了,我就說這麼多了,一切都掌握在你手中,用你的智慧來優化我們的冗余代碼吧,因為這樣它看起來相當不錯.
附:完整代碼:(我把ViewPath類放在同一個文件VIEwJump.Java裡,代碼上面已經給出)
public class VIEwJump{
private static ViewPath[] vIEwPath = null;
//私有構造函數
private VIEwJump(){}
//尋找該Panel是不是在路徑中
/**
* 找到了返回在實例數組中的下標
* 沒有找到返回-1
* @param pnl
* @return
*/
private static int find(JPanel pnl){
// System.out.println("執行find() in VIEwJump");
if(viewPath == null || vIEwPath.length==0) return -1;
for(int i = 0;i ViewPath vp = vIEwPath[i]; //對該路徑檢查
if(vp.find(pnl) != -1){
return i;
}
}
return -1;
}
//建立一個新的路徑
/**
*
* @param myPanel
* @param myVIEw
*/
private static int newPath(JPanel myPanel,BaseView myVIEw){
System.out.println("執行newPath() in VIEwJump");
//檢驗一下看有沒有無效的路徑,有則清除
if(viewPath == null || vIEwPath.length==0) {
viewPath = new ViewPath[]{new ViewPath(myPanel,myVIEw)};
return 0;
}
ViewPath[] vjArr = new ViewPath[vIEwPath.length];
int count = 0;
for(int i = 0;i if(viewPath[i].bsVIEw!=null){ //把不為空的值取出來
vjArr[count++] = vIEwPath[i];
}
}
viewPath = new VIEwPath[count+1];
System.arraycopy(vIEwPath,0,vjArr,0,count); //復制到原來的數組變量中
//最後一個位置留給新加入的元素
viewPath[count] = new ViewPath(myPanel,myVIEw);
return count;
}
//獲得實例的方法
/**
* 必須檢查該Panel是不是已經在路徑中了,如果在路徑中,
* 則返回注冊的編號,用此編號扁可以訪問到正確的類型了
* 如果不在路徑中,則以此為開始新建一個新的路徑
* 本來檢查路徑的時候沒有必要檢查路徑的第一個元素,
* 因為一個元素不可能是開端,但是為了防止用戶連續兩次registerPath的錯誤
* 請把第一個元素也給檢查一下
* myView 參數只有當該界面為跳轉的起始點時才需要,否則保持原始的VIEw
* @param me
* @param other
* 返回實例數組的下標,
*/
public static int registerPath(JPanel myPanel,BaseView myVIEw){
System.out.println("執行registerPath() in VIEwJump");
int idx = find(myPanel);
System.out.println("idx="+idx);
if(idx==-1){ //返回-1表示沒有找到,建立一個新的路徑
System.out.println("新建一個路徑");
idx = newPath(myPanel,myVIEw);
}
System.out.println("執行完注冊路徑..");
return idx; //返回實例下標
}
//設定要跳轉的下一個目標
public static void setNext(int id,JPanel aim){
if(id<0||id>=vIEwPath.length){
return;
}
ViewPath vp = vIEwPath[id];
//設定目標,從這裡看,這是存在安全漏洞的,如果使用者亂傳遞id進來的話
JPanel[] path = vp.pnlPath;
path[vp.index+1] = aim;
}
//回到上一個
public static void back(int id){
if(id<0||id>=vIEwPath.length){
return;
}
ViewPath vp = vIEwPath[id];
//回到上一個界面
if(vp.index>0){ //只有當前面有路徑時才作
vp.bsVIEw.remove(vp.pnlPath[vp.index]); //移去當前的
vp.index--; //游標往前走一步
vp.bsVIEw.add(vp.pnlPath[vp.index],BorderLayout.CENTER); //增加當前的到界面
vp.bsVIEw.validate();
vp.bsVIEw.repaint();
}
}
//回到起源處
public static void backHome(int id){
if(id<0||id>=vIEwPath.length){
return;
}
ViewPath vp = vIEwPath[id];
//直接回到第一步,需要清除該路徑嗎?中途斷裂怎麼辦?辦法是檢查VIEw是否已為空
//選擇不清除,每次在建立新的路徑時,檢查路徑是不是已經無效了
vp.bsVIEw.remove(vp.pnlPath[vp.index]); //移去當前的
vp.index = 0; //游標往前走一步
vp.bsVIEw.add(vp.pnlPath[vp.index],BorderLayout.CENTER); //增加當前的到界面
vp.bsVIEw.validate();
vp.bsVIEw.repaint();
}
//跳轉到下一處
public static void jump(int id){
if(id<0||id>=vIEwPath.length){
return;
}
ViewPath vp = vIEwPath[id];
if(vp.pnlPath[vp.index+1]==null){
return; //下一步根本沒有設置
}
vp.bsVIEw.remove(vp.pnlPath[vp.index]); //移去當前的
vp.index++;
vp.bsVIEw.add(vp.pnlPath[vp.index],BorderLayout.CENTER);
vp.bsVIEw.validate();
vp.bsVIEw.repaint();
}
}