設計迷蹤:給JAVA設計開發新手的一些建議和意見(三)

scud(飛雲小俠) 2005-7-23 http://www.jscud.com 轉載請註明作者

【空接口的使用】

 在接口使用的時候,空接口有2種情況:
 1.類似Cloneable,Serializable,他們往往是做一個標記,表示需要某個功能.當然你也可以這麼用,來表示你的類具有某個功能,實現了你的某個接口.
 2.你的接口繼承了別的接口(非空),你的接口本身沒有聲明函數.這種情況一般是你不希望用戶使用父接口來作爲參數類型,因爲他們的用途可能不同,此時就可以用空接口來實現.
 
 第一種情況我們不再多說,搜索一下關於Cloneable,Serializable的文章就會了解很多.
 我們來看下面的代碼:

  public interface Text
  {
   String getText();
  }
  
  public interface SqlText extends Text
  {
  }

 
 可以看到,Text接口是用於返回一個字符串.而SqlText是一個空接口,它繼承了Text接口.也就是說SqlText也是一種Text.但是我們可以知道,任何一個字符串不一定是Sql字符串,所以此時聲明瞭一個SqlText接口來用於表名當前的字符串是一個Sql字符串.你的函數可以這樣聲明: 


  public void doQuery(SqlText aSqlText)

而不是這樣


  
  public void doQuery(Text aText)

避免用戶產生歧義的想法,一眼看去,就明白應該傳入一個Sql字符串.
 
  


【繼承層次過多】
 一般來說,繼承的層次不要過多,否則使用者可能會討厭,找一個函數會很麻煩.很多Java語言檢查工具都建議你的繼承層次不要超過3層.
 
 
【Has A ,Is A,不要濫用繼承】

 "我是一個Mp3","我有一個Mp3",其實很容易分辨.但是在實際應用中,往往存在把"我有一個Mp3"的情況當作"我是一個Mp3",或者是爲了偷懶方便而放鬆了對自己的要求,甚至還沾沾自喜,感覺找到一個捷徑.(scud以前也幹過這種事情).
 
 以前我曾經這樣幹過:我的邏輯類直接繼承了我的數據庫訪問類,這樣我可以直接在邏輯類裏面訪問:
 

  public MyLogic extends MyDBA
  
  aLogic.getInt("click");
  aLogic.getString("name");
 

 看起來是非常方便,但是你的邏輯類就牢牢綁在了DBA上,是一種非常不好的做法.現在我這樣聲明:
  

  public MyLogic
   
   MyDBA adba;
   
   adba.getInt("click");
   adba.getString("name");
 

 其實代碼改動不大,但是你的邏輯類不在牢牢綁在DBA身上了,何樂而不爲.
 
 其實這種現象在開發人員中間可能經常見到,我們要儘量避免.下面再來看一個例子:
 
 //一個保存分頁信息的類
 

 public class PageInfo
 {
  private int page;
  private int pageCount;
  private int recPerPage;
  private int recCount;  
  
  //get,set method list...
 }
 

 一般的情況是,在Dao中進行分頁查詢,計算總記錄,總頁數等等,所以需要把PageInfo傳給Dao.而在邏輯類中,把傳回來的分頁信息數據推到FormBean或者是Action中.
 也許你會這麼想,如果我的Action或者FormBean繼承了PageInfo,豈不是要省很多事.
 
 千萬別這麼幹.並不是所有的動作都需要分頁信息,你的FormBean和PageInfo沒有繼承的關係.也就是說FormBean Has A PageInfo,但是不是Is A PageInfo.
 
 

【保持外觀/行爲一致】

 外觀一致其實很容易理解,例如你用size()表示得到一個List的大小,那麼在所有的List類中你都用size()得到它的大小,這就是外觀一致.
 外觀一致讓用戶更方便使用你的函數庫,不用記住幾個不同的表示同一個功能的函數名字.或者幾個名字相同功能卻不同的函數.那就很糟糕了.
 
 行爲一致相對外觀一致就相對比較難做到,但是優秀的設計師肯定會讓他的成果行爲一致,而不是出人意料的行爲,也不是一套強行規定的行爲.
 
 我們來看下面的代碼:
 

 
   import java.util.HashMap;
   import java.util.Map;
   
   

 

   class UserInfo
   {
    private String realname;
    
    public UserInfo(String sName)
    {
     this.realname = sName;
    }
    
    public void setName(String sName)
    {
     this.realname = sName;
    }
    public String getName()
    {
     return this.realname;
    }  
   }
   
   
   public class MyTest
   {
      
    Map userInfoMap = new HashMap(); 
   
    public void setUserInfo(String sName,UserInfo aInfo)
    {
     userInfoMap.put(sName,aInfo);
     
        userInfoMap.put(aInfo.getName(),aInfo);
    }
    
    public UserInfo getUserInfo(String sName)
    {
     return (UserInfo)userInfoMap.get(sName);
    } 
    
    public static void main(String args[])
    {
     MyTest aTest = new MyTest();
     
     UserInfo aUserInfo = new UserInfo("王小二");
     
     aTest.setUserInfo("兒童團團長",aUserInfo);
     aTest.setUserInfo("三班班長",aUserInfo);
     
     UserInfo 兒童團團長 = aTest.getUserInfo("兒童團團長");
     
     if(null!=兒童團團長)
     {  
         System.out.println(兒童團團長.getName());
     }
     else
     {
         System.out.println("兒童團團長 Not Found");
     }
     
     UserInfo 王小二 = aTest.getUserInfo("王小二");
     
     if(null!=王小二)
     {  
         System.out.println(王小二.getName());
     }
     else
     {
         System.out.println("王小二 Not Found");
     }
     
    }
   }
     

 
 可以看到,上面的代碼運行結果是"王小二",也就是說兒童團團長是王小二,王小二本身也是王小二,這一切正常.
 
 現在我們把setUserInfo裏面的第一句註釋掉:
 
 
    public void setUserInfo(String sName,UserInfo aInfo)
    {
     //userInfoMap.put(sName,aInfo);
     
        userInfoMap.put(aInfo.getName(),aInfo);
    }
    

 再次運行上面的代碼,我們發現兒童團團長不存在了,但是王小二還在.還可以看出,如果找"三班班長"的話,肯定也找不到,也就是說只有依據王小二的真名才能找到王小二,其他方法就不行了.
 
 從上面的setUserInfo和getUserInfo分析,如果採用修改後的代碼,我們的程序就出現了行爲表現不一致,而這是令人迷惑不解的,我們set了半天,卻找不到,豈不是令人惱火!
 
 當然上面的代碼比較簡單,通過簡單的修改就能做到行爲一致,但在實際編程中,往往因爲複雜的行爲操作,經常會造成行爲不一致,從而給開發人員帶來困惑.
 

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章