熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java核心技術 >> 正文

敏捷開發的必要技巧:處理不合適的依賴

2022-06-13   來源: Java核心技術 

  摘要

  要判斷一個代碼是不是包含了不合適的依賴共有四個方法看代碼有沒有互相依賴?認真想想它真正需要的是什麼?推測一下它在以後的系統中可以重用嗎?到要重用的時候就知道了現在我要重用這個類能不能重用?

  如果現在有一個類Parent裡面有個屬性的類型是Childadd的方法裡面還有個參數的類型是Girl

  class Parent{
        Child child;
        void add(Girl girl){
          
        }
     }

  因為上面Parent裡面用到了Child跟Girl這兩個類我們就說Parent引用了類Child跟類Girl現在的問題是如果Child這個類或者Girl這個類編譯不過的話那麼Parent這個類也編譯不了了也就是說Parent依賴於Child跟Girl這章講述的就是因為一些類的依賴造成的無法重用的問題

示例

  這是一個處理ZIP的程序用戶可以在主窗口中先輸入要生成的目標zip的路徑比如c:\fzip 然後輸入他想壓縮到這個zip的源文件的路徑比如
c:\fdoc和c:\fdoc 然後這個程序就會開始壓縮fdoc和fdoc生成fzip文件在壓縮各個源文件的時候主窗口下的狀態欄都要顯示相關的信息比如在壓縮c:\fdoc的時候狀態欄就顯示正在壓縮 c:\fzip                                                                              

目前的代碼就是                                                                            

  class ZipMainFrame extends Frame {
       StatusBar sb;
       void makeZip() {
           String zipFilePath;
           String srcFilePaths[];
           //根據UI上給zipFilePath和srcFilePaths賦值
          
           ZipEngine ze = new ZipEngine();
           zemakeZip(zipFilePath srcFilePaths this);
       }
       void setStatusBarText(String statusText) {
           sbsetText(statusText);
       }
    }  
    
    class ZipEngine {
       void makeZip(String zipFilePath String srcFilePaths[] ZipMainFrame f) {
           //在該路徑上創建zip文件 
            
           for (int i = ; i < srcFilePathslength; i++) { 
               //將srcFilePaths[i]的文件加到壓縮包中
                
               fsetStatusBarText(Zipping +srcFilePaths[i]); 
           }
       }
    }

  我們還有一個存貨管理系統裡面有一些程序的數據文件經常需要壓縮起來備份這些源數據文件都有固定的路徑所以就不需要用戶特地去輸入路徑了現在我們想直接把上面的這個ZipEngine類拿過來重用這個存貨管理系統也有一個主窗口同樣在壓縮待備份文件時狀態欄上面也要顯示目前正在壓縮的文件名稱

  現在問題來了我們希望可以在不用修改代碼的情況下直接重用ZipEngine這個類但看了上面的代碼以後我們發現在調用makeZip這個方法時還需要一個傳遞一個ZipMainFrame類型的參數進來可是很明顯我們現在的這個存貨管理系統裡面並沒有ZipMainFrame這樣的類也就是說現在ZipEngine這個類在我們的這個存貨管理系統中用不了了

  再往遠一點想好像其他的系統一般也不會有ZipMainFrame這個類即使類名一樣的裡面所做的功能也不一樣那其他的系統也重用不了這個ZipEngine類了

不合適的依賴讓代碼很難被重用

  因為ZipEngine引用了ZipMainFrame這個類當我們想重用ZipEngine的時候我們就需要將ZipMainFrame也加進來調用ZipEngine的makeZip方法時還要構造一個ZipMainFrame對象傳給它而在新的環境中我們不可能有一個同樣的ZipMainFrame也不可能特地為了調用這個方法隨便創建一個ZipMainFrame對象給它

  一般來說如果一個類A引用了一個類B當我們想要重用A這個類時我們就還得將B這個類也加進我們的系統如果B引用了C那麼B又將C也一起拉了進來而如果B或者C在一個新的系統中沒有意義或者壓根兒不應該存在的情況下真正我們想要用的A這個類也用不了了

  因此不合適的依賴讓代碼很難被重用

  為了可以重用ZipEngine首先我們得讓ZipEngine不再引用ZipMainFrame或者說讓ZipEngine不用依賴於ZipMainFrame
  那怎麼做呢?回答這個問題之前我們先回答另一個問題給你一段代碼你怎麼判斷這段代碼是不是包含了不合適的依賴不合適這個詞定義的標准又是什麼?


怎麼判斷是不合適的依賴

  方法
  一個簡單的方法就是我們先看一下這段代碼裡面有沒有一些互相循環的引用比如ZipMainFrame引用了ZipEngine這個類而ZipEngine又引用了ZipMainFrame我們管這樣的類叫互相依賴互相依賴也是一種代碼異味我們就認定這樣的代碼不合適的依賴
  這個方法很簡單不過這種方法並不能包含全部情況並不是所有有不合適的依賴的代碼都是這種互相依賴

  方法
  另一個方法比較主觀在檢查代碼的時候我們問自己對於它已經引用的這些類是它真正需要引用的嗎?對於ZipEngine它真的需要ZipMainFrame這個類嗎?ZipEngine只是改變ZipMainFrame的狀態欄上的信息是不是只有引用了ZipMainFrame才能滿足這樣的需求其他類行不行?有沒有一個類可以取代ZipMainFrame呢?而實際上ZipEngine並不是一定要引用ZipMainFrame的它想引用的其實只是一個可以顯示信息的狀態欄而已
因此我們就將代碼改為

  class ZipEngine {
void makeZip(String zipFilePath String srcFilePaths[] StatusBar statusBar) {
           //在該路徑上創建zip文件
            
           for (int i = ; i < srcFilePathslength; i++) {
               //將srcFilePaths[i]的文件加到壓縮包中
                
               statusbarsetText(Zipping +srcFilePaths[i]);
           }
       }
    }        

  現在ZipEngine只是引用了StatusBar而不再是ZipMainFrame了可是這樣好嗎?相對好一些!因為StatusBar比較通用(至少有StatusBar這個類的系統比ZipMainFrame多多了)這樣的話ZipEngine這個類的可重用性就大幅改觀了
  不過這樣的方法還是太主觀了沒有一個既定的標准可以判斷ZipEngine到底需要的是什麼樣的東西比如我們就說ZipEngine其實想要的也不是一個狀態欄它只是想調用一個可以顯示一些信息的接口而已(而不是一個狀態欄這麼大的一個對象)                                                                    

  方法

  第種方法也很主觀在設計類的時候我們先預測一個以後可能會重用這個類的系統然後再判斷在那樣的系統中這個類能不能被重用?如果你自己都覺得以後的系統不能重用這個類的話你就斷定這個類包含不合適的依賴
  比如我們在設計完ZipEngine這個類時我們就想一下這個類能在別的系統重用嗎?可是好像別的系統不會有ZipMainFrame這個類至少一個沒有GUI的系統會有這樣的類!這樣的話那它就不應該引用ZipMainFrame這個類了這個方法其實也很主觀不怎麼實用每個人預測的可能性都不一樣

  方法

  第個方法比較簡單而且客觀了當我們想在一個新系統中重用這個類卻發現重用不了時我們就判斷這個類包含了不合適的依賴比如我們在存貨管理系統中要重用ZipEngine的時候我們才發現這個類重用不了這時我們就認定這個類有不合適的依賴

  後一種方法是個懶惰而被動的方法因為我們真正想在具體的項目中重用的時候才能判斷出來不過這也是個很有效的方法


總結

  要判斷一個代碼是不是包含了不合適的依賴共有四個方法
   看代碼有沒有互相依賴?

   認真想想它真正需要的是什麼?

   推測一下它在以後的系統中可以重用嗎?

   到要重用的時候就知道了現在我要重用這個類能不能重用?

  方法是最簡單的方法推薦初學者可以這樣來判斷有更多的設計經驗了再用方法會好一些

怎麼讓ZipEngine不再引用(依賴於)ZipMainFrame

  現在我們來看看怎麼讓ZipEngine不再引用ZipMainFrame其實在介紹方法的時候我們就已經通過思考發現ZipEngine這個類真正需要的是什麼也找出了解決辦法不過因為方法相對來講並不是那麼簡單就可以用好的所以我們先假裝不知道方法的結果

  我們用方法現在我們是在做一個文字模式的系統(沒有狀態欄了我們只能直接在沒有圖形的屏幕上顯示這些信息)發現ZipEngine不能重用了怎麼辦?

  因為我們不能重用ZipEngine我們只好先將它的代碼復制粘貼出來然後再修改成下面的代碼

  class TextModeApp {
       void makeZip() {
           String zipFilePath;
           String srcFilePaths[];
             
           ZipEngine ze = new ZipEngine();
           zemakeZip(zipFilePath srcFilePaths);
       }        
    }          
    class ZipEngine {
       void makeZip(String zipFilePath String srcFilePaths[]) {
           //在該路徑上創建zip文件
             
           for (int i = ; i < srcFilePathslength; i++) {
               //將srcFilePaths[i]的文件加到壓縮包中
              
               Systemoutprintln(Zipping +srcFilePaths[i]);
       } 
    }  


  再看一下原來的代碼是

  class ZipEngine {
       void makeZip(String zipFilePath String srcFilePaths[] ZipMainFrame f) {
           //在該路徑上創建zip文件
          
           for (int i = ; i < srcFilePathslength; i++) {
               //將srcFilePaths[i]的文件加到壓縮包中 
              
               fsetStatusBarText(Zipping +srcFilePaths[i]);
           }
       }
    }

  很明顯這裡面有很多的重復代碼(代碼異味)要消除這樣的代碼異味我們就先用偽碼讓這兩段
代碼看起來一樣比如改成

  class ZipEngine {
       void makeZip(String zipFilePath String srcFilePaths[]) {
           //在該路徑上創建zip文件
          
           for (int i = ; i < srcFilePathslength; i++) {
               //將srcFilePaths[i]的文件加到壓縮包中
              
               顯示信息
           }
       }
    }        

  因為顯示信息具體出來有兩種實現所以我們現在創建一個接口裡面有一個方法用來顯示信息這個方法可以直接取名為showMessage而根據這個接口做的事我們也可以直接將接口名取為MessageDisplay或者MessageSink之類的

    interface MessageDisplay {                                                                    
       void showMessage(String msg);                                                              
    }                                                                                              

  將ZipEngine改為

  class ZipEngine {
       void makeZip(String zipFilePath String srcFilePaths[] MessageDisplay
    msgDisplay) {
           //在該路徑上創建zip文件
          
           for (int i = ; i < srcFilePathslength; i++) {
               //將srcFilePaths[i]的文件加到壓縮包中
                
               msgDisplayshowMessage(Zipping +srcFilePaths[i]);
           }    
       }        
    }  


  而MessageDisplay這個接口的兩個實現類就是

  class ZipMainFrameMessageDisplay implements MessageDisplay {
       ZipMainFrame f;
       ZipMainFrameMessageDisplay(ZipMainFrame f) {
           thisf = f;
       }        
       void showMessage(String msg) {
           fsetStatusBarText(msg);
       }        
    }          

    class SystemOutMessageDisplay implements MessageDisplay {
       void showMessage(String msg) {
           Systemoutprintln(msg);
       }        
    }

  現在兩個系統也相應的做了修改

  class ZipMainFrame extends Frame {
       StatusBar sb;
       void makeZip() {
           String zipFilePath;
           String srcFilePaths[];
           //根據UI上給zipFilePath和srcFilePaths賦值
             
           ZipEngine ze = new ZipEngine();
 zemakeZip(zipFilePath srcFilePaths new ZipMainFrameMessageDisplay(this));
       }        
       void setStatusBarText(String statusText) {
           sbsetText(statusText);
       }        
    }        
    
    class TextModeApp {
       void makeZip() {
           String zipFilePath;
           String srcFilePaths[];
             
           ZipEngine ze = new ZipEngine();
           zemakeZip(zipFilePath srcFilePaths new SystemOutMessageDisplay());
       }        
    }

  改進後的代碼

下面就是改進完的代碼為了讓代碼看起來清楚一些我們用了Java的內類

  interface MessageDisplay {
       void showMessage(String msg); 
    }

    class ZipEngine {
       void makeZip(String zipFilePath String srcFilePaths[] MessageDisplay
    msgDisplay) { 
           //在該路徑上創建zip文件
          
           for (int i = ; i < srcFilePathslength; i++) {
               //將srcFilePaths[i]的文件加到壓縮包中
              
               msgDisplayshowMessage(Zipping +srcFilePaths[i]); 
           }
       } 
    }            
    
    class ZipMainFrame extends Frame {
       StatusBar sb; 
       void makeZip() { 
           String zipFilePath; 
           String srcFilePaths[];
           //根據UI上給zipFilePath和srcFilePaths賦值 
          
           ZipEngine ze = new ZipEngine();
           zemakeZip(zipFilePath srcFilePaths new MessageDisplay() {
               void showMessage(String msg) {
                  setStatusBarText(msg);
               }
           });
       }
       void setStatusBarText(String statusText) {
           sbsetText(statusText);
       }
    }            
    
    class TextModeApp {
       void makeZip() {
           String zipFilePath; 
           String srcFilePaths[];
          
           ZipEngine ze = new ZipEngine(); 
           zemakeZip(zipFilePath srcFilePaths new MessageDisplay() {
               void showMessage(String msg) {
                  Systemoutprintln(msg);
               }
           });
       }
    }

  引述                                                                                        

 依賴反轉原則(Dependency Inversion Principle )表述抽象不應該依賴於具體高層的比較抽象的類不應該依賴於低層的比較具體的類當這種問題出現的時候我們應該抽取出更抽象的一個概念然後讓這兩個類依賴於這個抽取出來的概念更多的信息可以看

;                                     


From:http://tw.wingwit.com/Article/program/Java/hx/201311/26899.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.