隨筆-30  評(píng)論-123  文章-0  trackbacks-0
          摘要:雖然Java不允許多重繼承,但是有些情況卻允許其使用。本文將闡述在一個(gè)Web應(yīng)用中模擬多重繼承的技術(shù)。

          在一般的開(kāi)發(fā)中,Java的單繼承限制一般不會(huì)引起什么問(wèn)題。實(shí)際上,需要使用多重繼承往往意味著糟糕的設(shè)計(jì)。然而還是存在一些情況,程序員們希望能夠繼承多個(gè)類。雖然Java不允許繼承多個(gè)類,但是有些技巧能夠模擬多重繼承。

          我在Swing應(yīng)用程序和Web應(yīng)用中都使用過(guò)這個(gè)技巧。Swing應(yīng)用打包并部署服務(wù)到應(yīng)用服務(wù)器上。這種情況下,因?yàn)槲蚁M诓煌M件間拖放對(duì)象而這些GUI組件共享同一個(gè)拖放方法,這樣的話,所有GUI組件需要擴(kuò)展兩個(gè)類:GUI組件本身(JTree或JList)和通用的拖放類(drag-and-drop class)。本文中介紹的技術(shù)簡(jiǎn)化了拖放類的實(shí)現(xiàn)。

          為了更好的闡述Java中多重繼承的技術(shù),讓我們先來(lái)如何在一個(gè)Web應(yīng)用中使用它。在這個(gè)Web應(yīng)用中,Servlet類和其他類需要被擴(kuò)展。我的這個(gè)應(yīng)用很簡(jiǎn)單,是一個(gè)基于文本的信息傳遞系統(tǒng),能夠通過(guò)移動(dòng)電話、PDA或者其他網(wǎng)絡(luò)終端設(shè)備傳遞信息給另一個(gè)移動(dòng)電話。
          jw-1024-multiple.jpg
          如上圖所示,這個(gè)系統(tǒng)的核心是一個(gè)能夠從客戶端接受信息然后傳遞給移動(dòng)電話的服務(wù)。為了簡(jiǎn)化客戶端開(kāi)發(fā),我寫了一個(gè)MessageClient類來(lái)包含所有用于與服務(wù)通信的通用方法。因?yàn)檫@個(gè)類可以最為基類被所有可能的終端設(shè)備使用,因此能夠簡(jiǎn)化客戶端開(kāi)發(fā)。
          MessageClient類的源代碼如下:
          import?java.rmi.Naming;

          public?abstract?class?MessageClient
          {
          ???
          private?MessageServer?messageServer;

          ???
          public?MessageClient()
          ???
          {
          ??????System.out.println(
          "Initializing?Message?Client");
          ???}

          ???
          /**
          ????*?Method?used?to?connect?to?the?message?server
          ????*
          ????*?
          @param?serverName?name?of?the?server?that?contains?the?message?server
          ????
          */

          ???
          protected?void?connectToServer()
          ???
          {
          ??????String?serverName?
          =?getServerName();
          ??????
          try
          ??????
          {
          ?????????String?name?
          =?"//"?+?serverName?+?"/MessageServer";
          ?????????messageServer?
          =?((MessageServer)?Naming.lookup(name));
          ??????}

          ??????
          catch(Exception?e)
          ??????
          {
          ?????????System.out.println(
          "Error?connecting?to?Message?Server.??Exception?is?"?+?e);
          ?????????e.printStackTrace();
          ??????}

          ???}


          ???
          /**
          ????*?Method?used?to?send?message?to?server
          ????*
          ????*?
          @param?phoneNum?phone?number?to?send?message?to
          ????*?
          @param?message?message?to?send
          ????
          */

          ???
          public?boolean?sendMessage(String?phoneNum,?String?message)
          ???
          {
          ??????
          try
          ??????
          {
          ?????????
          return(messageServer.sendMessage(phoneNum,message));
          ??????}

          ??????
          catch(Exception?e)
          ??????
          {
          ?????????System.out.println(
          "Error?Sending?Message.??Exception?is?"?+?e);
          ?????????e.printStackTrace();
          ?????????
          return(false);
          ??????}

          ???}


          ???
          public?abstract?String?getServerName();
          }
          ?
          這個(gè)類包含3個(gè)方法:
          sendMessage()方法將實(shí)際信息發(fā)送到服務(wù)器;
          connectServer()方法負(fù)責(zé)連接服務(wù)。在本例中,服務(wù)是一個(gè)RMI服務(wù);
          getServerName()是一個(gè)抽象類,因?yàn)椴煌O(shè)備決定服務(wù)名字的方式不同。也就是說(shuō),任何繼承自MessageClient的類都必須實(shí)現(xiàn)getServerName()方法。

          在開(kāi)發(fā)與信息服務(wù)通信的客戶端程序時(shí),我們需要多重繼承。我們的網(wǎng)絡(luò)客戶端是一個(gè)簡(jiǎn)單的用來(lái)接收來(lái)自表單的信息并將其傳送到服務(wù)器的Servlet。為了完成上述任務(wù),這個(gè)Servlet必須既繼承HttpServlet又繼承MessageClient。由于Java不允許這種繼承行為,我讓主類繼承HttpServlet,讓主類中的內(nèi)部類繼承MessageClient,然后外部類創(chuàng)建了內(nèi)部類的一個(gè)實(shí)例。代碼如下:
          public?class?SendMessageServlet?extends?HttpServlet{

          ???
          private?MessageClient?m_messageClient;
          ???
          private?String?m_serverName;

          ???
          public?void?doGet(HttpServletRequest?request,?HttpServletResponse?response)
          ?????????
          throws?ServletException,?IOException{
          ??????
          try{
          ????????
          //Get?server?name
          ?????????m_serverName?=?request.getServerName();
          ?????????System.out.println(
          "ServerName?is?"?+?m_serverName);
          ?????????
          //Create?message?client?to?communicate?with?message?server
          ?????????m_messageClient?=?new?ServletMessageClient();
          ?????????System.out.println(
          "Created?Message?Client");
          ?????????m_messageClient.connectToServer();

          ?????????
          //Get?message?and?phone?number
          ?????????String?phoneNum?=?(String)?request.getParameter("PhoneNum");
          ?????????String?message?
          =?(String)?request.getParameter("Message");

          ?????????
          //Send?message
          ?????????m_messageClient.sendMessage(phoneNum,message);

          ?????????
          //Display?page?to?tell?user?message?was?sent
          ?????????response.setContentType("text/html");
          ?????????RequestDispatcher?dispatcher?
          =?getServletContext().getRequestDispatcher("/SendMessageForm.jsp");
          ?????????dispatcher.include(request,?response);
          ??????}
          catch?(Exception?e){
          ?????????e.printStackTrace();
          ??????}

          ???}
          ?

          ???
          public?void?doPost(HttpServletRequest?request,?HttpServletResponse?response)
          ?????????
          throws?ServletException,?IOException???{
          ??????doGet(request,?response);
          ???}


          ????
          /**?Inner?class?used?to?extend?MessageClient?????*/
          ????
          public?class?ServletMessageClient?extends?MessageClient?{
          ???????
          public?ServletMessageClient(){
          ?????????
          super();
          ???????}

          ???????
          public?String?getServerName(){
          ??????????System.out.println(
          "Returning?ServerName?"?+?m_serverName);
          ??????????
          return(m_serverName);
          ???????}

          ????}

          }
          ?
          這種方法并不是真正的多重繼承,因?yàn)槲覀兪褂昧舜?例如:MessageClient被很多外部類繼承,但并不是外部類本身),但是效果是一樣的。雖然MessageClient能被單獨(dú)的類擴(kuò)展,但是使用內(nèi)部類允許它訪問(wèn)外部類中的所有成員和方法。這讓兩個(gè)類之間的交互變得很容易。

          這個(gè)例子只擴(kuò)展了兩個(gè)類,你當(dāng)然可以使用這種技術(shù)擴(kuò)展任意多類。

          當(dāng)然,其實(shí)這個(gè)信息傳遞服務(wù)的例子不用多重繼承也能實(shí)現(xiàn)。如果MessageClient有一個(gè)能夠接收服務(wù)服務(wù)名字的構(gòu)造函數(shù),那么getServerName()方法就可以不是抽象的。這意味著Web客戶端可以不必繼承MessageClient類。

          最后提醒開(kāi)發(fā)者,只有在有明確的理由,確定要使用上面技術(shù)時(shí)才使用,并且使用時(shí)要小心。因?yàn)槎嘀乩^承使設(shè)計(jì)變得復(fù)雜,并且很容易被誤用。
          posted on 2006-05-16 11:40 學(xué)二的貓 閱讀(8360) 評(píng)論(6)  編輯  收藏 所屬分類: Java禪機(jī)

          評(píng)論:
          # re: 在Java中模擬多重繼承--擴(kuò)展多個(gè)類的技巧 2006-05-16 12:53 | wolfsquare
          在這里似乎沒(méi)有多重繼承的必要.MessageClient已經(jīng)能完成所有功能了.  回復(fù)  更多評(píng)論
            
          # re: 在Java中模擬多重繼承--擴(kuò)展多個(gè)類的技巧 2006-05-16 13:02 | 學(xué)二的貓
          @wolfsquare
          我在文章的最后也提到過(guò)了,
          “當(dāng)然,其實(shí)這個(gè)信息傳遞服務(wù)的例子不用多重繼承也能實(shí)現(xiàn)”。
          這么寫只是舉個(gè)例子,設(shè)計(jì)良好的Java應(yīng)用絕對(duì)不會(huì)用到多重繼承。  回復(fù)  更多評(píng)論
            
          # re: 在Java中模擬多重繼承--擴(kuò)展多個(gè)類的技巧 2006-05-16 17:55 | wolfsquare
          既然你把自己的心得寫出來(lái),那么必然覺(jué)得它有所用途.
          既然它有所用途,那么自己就不會(huì)說(shuō)出它沒(méi)有用的話,否則你說(shuō)一個(gè)沒(méi)有用的東西出來(lái)有什么意義呢,基于想讓它有意義的理由,你的例子需要再舉得合適一些。
            回復(fù)  更多評(píng)論
            
          # re: 在Java中模擬多重繼承--擴(kuò)展多個(gè)類的技巧 2006-05-16 18:54 | 學(xué)二的貓
          @wolfsquare
          模擬多重繼承的這個(gè)技術(shù)是為解決一個(gè)實(shí)際項(xiàng)目中的問(wèn)題而產(chǎn)生的,當(dāng)然有他的實(shí)際意義。

          實(shí)際項(xiàng)目中由于一開(kāi)始的設(shè)計(jì)存在問(wèn)題,導(dǎo)致我們后期開(kāi)發(fā)時(shí)不得不引入多重繼承機(jī)制,因?yàn)檫@么做付出的代價(jià)最小。我知道最好的解決方案當(dāng)然是重構(gòu)這個(gè)工程,或者修改一大堆的接口和類,然后重新測(cè)試。同志們,如果你是項(xiàng)目經(jīng)理,你會(huì)這么做嗎??

          項(xiàng)目是以成敗論英雄的,做項(xiàng)目最講究用最小的投入達(dá)成既定目標(biāo)。在我的項(xiàng)目中,多重繼承這個(gè)解決方案付出的代價(jià)最小,盡管還存在其他許多解決方法,但是我們最后還是決定保留原來(lái)不好的設(shè)計(jì),采用多重繼承解決問(wèn)題。

          好與壞之間沒(méi)有嚴(yán)格的界限,同一樣?xùn)|西放到不同環(huán)境中,效果和評(píng)價(jià)是不一樣的。就像我在文章開(kāi)頭說(shuō)的一樣:“在一般的開(kāi)發(fā)中,Java的單繼承限制一般不會(huì)引起什么問(wèn)題。實(shí)際上,需要使用多重繼承往往意味著糟糕的設(shè)計(jì)。”但是在我的這個(gè)項(xiàng)目中,多重繼承確實(shí)最佳解決方案。

          我很抱歉給出的例子可能不是很恰當(dāng),因?yàn)檫@是我自己編的一個(gè)例子,目的是讓例子更好懂,如果我把項(xiàng)目里的代碼拿出來(lái),是的,那確實(shí)很恰當(dāng),但是那樣做的話會(huì)涉及到21個(gè)類3個(gè)接口,類里面最小的也有60句,多的有2400句代碼,我這樣貼出來(lái),一我想我說(shuō)不明白事情了--實(shí)際問(wèn)題確實(shí)太復(fù)雜,二恐怕要花費(fèi)你很多寶貴的時(shí)間,更糟糕的是,你還不一定能看明白。并且我也違反了公司的竟業(yè)守則和保密協(xié)議。

          請(qǐng)?jiān)徫乙粫r(shí)間確實(shí)找不出更好的例子,因?yàn)樾枰玫蕉嘀乩^承的情況極少極少,只在某些特殊情況下才會(huì)選擇使用多重繼承來(lái)解決問(wèn)題(例如我的這個(gè)項(xiàng)目,使用多重繼承代價(jià)最小)。我寫這篇文章的目的是想告訴大家Java存在這么一種技巧可以模擬多重繼承,如果有一天,你的項(xiàng)目碰到了和我類似的情況,你就可以不必重構(gòu)你的項(xiàng)目或者對(duì)項(xiàng)目大刀闊斧了,因?yàn)橛幸环N在某些特定情況下很有用的技巧--模擬多重繼承。  回復(fù)  更多評(píng)論
            
          # re: 在Java中模擬多重繼承--擴(kuò)展多個(gè)類的技巧 2006-05-16 20:58 | wolfsquare
          由于在本例中你舉例的場(chǎng)景不太合適,本來(lái)是解惑的東西卻讓人更迷惑甚至誤導(dǎo),豈不是更糟?
          期待你的改進(jìn)版本.  回復(fù)  更多評(píng)論
            
          # re: 在Java中模擬多重繼承--擴(kuò)展多個(gè)類的技巧 2006-05-17 08:50 | 學(xué)二的貓
          @wolfsquare
          我的文章讓你感到迷惑了?那是我寫得不好。令大家看不懂或者模棱兩可決不是我的本意。

          文中的例子是實(shí)際項(xiàng)目的簡(jiǎn)化,目的是能用簡(jiǎn)單的代碼說(shuō)明如何模擬多重繼承。

          我想看了這篇文章你應(yīng)該會(huì)對(duì)如何實(shí)現(xiàn)模擬實(shí)現(xiàn)多重繼承這個(gè)技巧本身有所領(lǐng)悟,至于這個(gè)技巧的應(yīng)用時(shí)機(jī),我確實(shí)很難用一個(gè)短小的例子恰當(dāng)?shù)某尸F(xiàn)出來(lái)--因?yàn)槿绻粋€(gè)小例子需要用到多重繼承,我完全可以重構(gòu)它讓他不用多重繼承就可以實(shí)現(xiàn),因?yàn)橹貥?gòu)的代價(jià)并不大。

          技巧本身我想大家都看得懂、學(xué)得會(huì),但是有沒(méi)有機(jī)會(huì)使用,就要看你實(shí)際開(kāi)發(fā)中能不能碰到像我一樣類似得情況或更復(fù)雜得情況了。

          如果你連模擬多重繼承這個(gè)技巧本身也沒(méi)有看懂,請(qǐng)您告訴我,我會(huì)重新思考,重寫本篇文章。因?yàn)閷戇@篇文章的目的只是想教大家這么一個(gè)技術(shù),應(yīng)用時(shí)機(jī)還得大家從實(shí)踐中去體會(huì)。  回復(fù)  更多評(píng)論
            
          主站蜘蛛池模板: 方山县| 辽宁省| 五峰| 泾源县| 卫辉市| 彭泽县| 丰顺县| 青河县| 五河县| 海南省| 汉中市| 阜康市| 阳谷县| 昂仁县| 申扎县| 库尔勒市| 阳西县| 文化| 尚义县| 威信县| 平舆县| 溆浦县| 嵊泗县| 盐池县| 漳浦县| 临汾市| 永宁县| 德兴市| 辽阳县| 阳春市| 濉溪县| 绵竹市| 道真| 南溪县| 和政县| 嫩江县| 洪雅县| 广南县| 贞丰县| 南溪县| 武隆县|