ivaneeo's blog

          自由的力量,自由的生活。

            BlogJava :: 首頁 :: 聯(lián)系 :: 聚合  :: 管理
            669 Posts :: 0 Stories :: 64 Comments :: 0 Trackbacks

          #

          范例(Examples):有局部變量(Using Local Variables)

          是的,就在局部變量,包括傳進源函數(shù)的參數(shù)和源函數(shù)所聲明的臨時變量。局部變量的作用域僅限于源函數(shù),所以當(dāng)我使用Extract Method(110)時,必須花費額外工夫去處理這些變量.某些時候它們甚至可能妨礙我,使我根本無法進行這項重構(gòu).

          局部變量最簡單的情況是:被提煉碼只是讀取這些變量的值,并不修改它們.這種情況下我可以簡單地將它們當(dāng)作參數(shù)傳給目標(biāo)函數(shù).所以如果我面對下列函數(shù):
          void printOwing() {
              Enumeration e = _orders.elements();
              double outstanding = 0.0;

              printBanner();

              // calculate outstanding
              while(e.hasMoreElements()) {
                 Order each = (Order) e.nextElement();
                 outstanding += each.getAmount();
              }

              //print details
             
          System.out.println("name:" + _name);
             
          System.out.println("amount" + outstanding);
          }

          我就可以將[打印詳細(xì)信息]這一部分提煉為[帶一個參數(shù)的函數(shù)]:

          void printOwing() {
              Enumeration e = _orders.elements();
              double outstanding = 0.0;

              printBanner();


              // calculate outstanding
              while(e.hasMoreElements()) {
                 Order each = (Order) e.nextElement();
                 outstanding += each.getAmount();
              }
             printDetails(outstanding);
          }

          void printDetails(double outstanding) {
              System.out.println("name:" + _name);
             
          System.out.println("amount" + outstanding);
          }

          必要的話,你可以用這種手法處理多個局部變量.

          posted @ 2005-08-24 15:13 ivaneeo 閱讀(223) | 評論 (0)編輯 收藏

          范例(Examples):無局部變量(No Local Variables)
          在最簡單的情況下,Extract Method(110)易如反掌。請看下列函數(shù):

          void printOwing() {
              Enumeration e = _orders.elements();
              double outstanding = 0.0;

              //print banner
              System.out.println("********************************");
              System.out.println("********* Customer Owes **********");
              System.out.println("********************************");

              // calculate outstanding
              while(e.hasMoreElements()) {
                 Order each = (Order) e.nextElement();
                 outstanding += each.getAmount();
              }

              //print details
             
          System.out.println("name:" + _name);
             
          System.out.println("amount" + outstanding);
          }

          我們可以輕松提煉出[打印banner]的代碼。我只需要剪切、粘貼、再插入一個函數(shù)調(diào)用動作就行了:

          void printOwing() {
              Enumeration e = _orders.elements();
              double outstanding = 0.0;

              printBanner();

              // calculate outstanding
              while(e.hasMoreElements()) {
                 Order each = (Order) e.nextElement();
                 outstanding += each.getAmount();
              }

              //print details
             
          System.out.println("name:" + _name);
             
          System.out.println("amount" + outstanding);
          }

          void printBanner() {
             
          //print banner
              System.out.println("********************************");
              System.out.println("********* Customer Owes **********");
              System.out.println("********************************");
          }
          posted @ 2005-08-24 14:59 ivaneeo 閱讀(197) | 評論 (0)編輯 收藏

          作法(Mechanics)
            • 創(chuàng)造一個新函數(shù),根據(jù)這個函數(shù)的意圖來給它命名(以它[做什么]來命名,而不是以它[怎么做]命名)。 --》即使你想要提煉(extract)的代碼非常簡單,例如只是一條消息或一個函數(shù)調(diào)用,只要新函數(shù)的名稱能夠以更好方式昭示代碼意圖,你也應(yīng)該提煉 它。但如果你想不出一個更有意義的名稱,就別動。
            • 將提煉出的代碼從源函數(shù)(source)拷貝到新建的目標(biāo)函數(shù)(target)中。
            • 仔細(xì)檢查提煉出的代碼,看看其中是否引用了[作用域(scope)限于源函數(shù)]的變量(包括局部變量和源函數(shù)參數(shù))。
            • 檢查是否有[僅用于被提煉碼]的臨時變量(temporary variables)。如果有,在目標(biāo)函數(shù)中將它們聲明為臨時變量。
            • 檢查被提煉碼,看看是否有任何局部變量(local-scope variables)的值被它改變。如果一個臨時變量值被修改了,看看是否可以被提煉碼處理為一個查詢(query),并將結(jié)果賦值給相關(guān)變量。如果很難 這樣做,或如果被修改的變量不止一個,你就不能僅僅將這段代碼原封不動地提煉出來。你可能需要先使用Split Temporary Variable(128),然后再嘗試提煉。也可以使用Replace Temp with Query(120)將臨時變量消滅掉。
            • 將被提煉碼中需要讀取的局部變量,當(dāng)作參數(shù)傳給目標(biāo)函數(shù)。
            • 處理完所有局部變量之后,進行編譯。
            • 在源函數(shù)中,將被提煉碼替換為[對目標(biāo)函數(shù)的調(diào)用]。--》如果你將任何臨時變量移到目標(biāo)函數(shù)中,請檢查它們原本的聲明式是否在被提煉碼的外圍。如果是,現(xiàn)在你可以刪除這些聲明式了。
            • 編譯,測試。
          posted @ 2005-08-24 11:04 ivaneeo 閱讀(199) | 評論 (0)編輯 收藏

          動機(Motivation)
          Extract Method是最常用的重構(gòu)手法之一。當(dāng)我看見一個過長的函數(shù)或者一段需要注釋才能讓人理解用途的代碼,我就會將這段代碼放進一個獨立函數(shù)中。

          有數(shù)個原因造成我喜歡簡短而有良好命名的函數(shù)。首先,如果每個函數(shù)的粒度都很小(finely grained),那么函數(shù)之間彼此復(fù)用的機會就更大;其次,這會使高層函數(shù)碼讀起來就像一系列注釋;再者,如果函數(shù)都是細(xì)粒度,那么函數(shù)的覆寫 (override)也會更容易些。

          一個函數(shù)多長才算合適?在我看來,長度不是問題,關(guān)鍵在于函數(shù)名稱和函數(shù)本體之間的語義距離(semantic distance)。如果提煉動作(extracting)可以強化代碼的清晰度,那就去做,就算函數(shù)名稱比提煉出來的代碼還長也無所謂。
          posted @ 2005-08-24 10:54 ivaneeo 閱讀(242) | 評論 (0)編輯 收藏

          Extract Method

          將這段代碼放進一個獨立函數(shù)中,并讓函數(shù)名稱解釋該函數(shù)的用途。

          void printOwing(double amount) {
              printBanner();

              //print details
              System.out.println("name:" + _name);
              System.out.println("amount" + amount);
          }

                                   |  |
                                   |  |
                                  \  /

          void printOwing(double amount) {

              printBanner();

              printDetails(amount);
          }

          void printDetails(double amount) {
              System.out.println("name:" + _name);

              System.out.println("amount" + amount);
          }
          posted @ 2005-08-24 10:33 ivaneeo 閱讀(198) | 評論 (0)編輯 收藏

           隨著人們對動態(tài)語言興趣的日益濃厚,越來越多的人都遇到了閉包(Closures )和或塊(Blocks)等概念。有著C/C++/Java/C#等語言背景的人因為這些語言本身沒有閉包這個概念,所以可能不太了解閉包。本文將簡單的介紹一下閉包的概念,那些有大量支持閉包語言編程經(jīng)驗的人也許覺得本文不會太有意思。

              閉包的概念已經(jīng)提出很長時間了。我第一次碰到這它是在smalltalk中,那時候還叫做塊(blocks)。Lisp語言中用的很多。Ruby中也有同樣的功能-這也是Ruby用戶喜歡Ruby的一個原因。

              本質(zhì)上來說,一個閉包是一塊代碼,它們能作為參數(shù)傳遞給一個方法調(diào)用。我將通過一個簡單的例子來闡述這個觀點。假設(shè)我們有一個包含一些雇員對象的列表,然后我想列出職位為經(jīng)理的員工,這樣的員工可以通過IsManager判斷。在C#里,我們可能會寫出下面類似的代碼:

            public static IList Managers(IList emps) {
              IList result = new ArrayList();
              foreach(Employee e in emps)
                if (e.IsManager) result.Add(e);
              return result;
            }
          

              在一種支持閉包的語言中,比如Ruby,我們可以這樣寫:

            def managers(emps)
          	return emps.select {|e| e.isManager}
            end
            

              select是Ruby中定義的集合結(jié)構(gòu)中的一個方法,它接受一個block,也就是閉包,作為一個參數(shù)。在Ruby中,閉包寫在一對大括號中(不止這一種方法,另一種為do .. end)。如果這個塊也接受參數(shù),你可以將這些參數(shù)放到兩個豎線之間。select方法循環(huán)迭代給定的數(shù)組,對每個元素執(zhí)行給定的block,然后將每次執(zhí)行block返回true的元素組成一個新的數(shù)組再返回。

              現(xiàn)在,如果你是C程序員你也許要想,通過函數(shù)指針也可以實現(xiàn),如果你是JAVA程序員,你可能回想我可以用匿名內(nèi)類來實現(xiàn),而一個C#者則會想到代理(delegate)。這些機制和閉包類似,但是它們和閉包之間有兩個明顯得區(qū)別。

              第一個是形式上的不同(The first one is a formal difference)。閉包可以引用它定義時候可見的變量。看看下面的方法:

          def highPaid(emps)
          	threshold = 150
          	return emps.select {|e| e.salary > threshold}
          end
            

              注意select的block代碼中引用了在包含它的方法中的局部變量,而其它不支持真正閉包的語言使用其它方法達(dá)到類似功能的方法則不能這樣做。閉包還允許你做更有趣的事情,比如下面方法:

          def paidMore(amount)
          	return Proc.new {|e| e.salary > amount}
          end
          

              這個方法返回一個閉包,實際上它返回一個依賴于傳給它的參數(shù)的閉包。我可以用一個參數(shù)創(chuàng)建一個這樣的方法,然后再把它賦給另一個變量。

          highPaid = paidMore(150)
          

              變量 highPaid 包含了一段代碼(在Ruby中是一個Proc對象),這段代碼將判斷一個對象的salary屬性是否大于150。我們可以這樣使用這個方法:

          john = Employee.new
          john.salary = 200
          print highPaid.call(john)
            

                表達(dá)式highPaid.call(john)調(diào)用我之前定義的代碼,這時候此代碼中的amount已經(jīng)在創(chuàng)建這方法的時候綁定為150。即使現(xiàn)在我執(zhí)行print 的時候,150已經(jīng)不在它的范圍內(nèi)了,但是amount和150之間的綁定依然存在。

              所以,閉包的第一個關(guān)鍵點是閉包是一段代碼加上和定義它的環(huán)境之間的綁定(they are a block of code plus the bindings to the environment they came from)。這是閉包和函數(shù)指針等其它相似技術(shù)的不同點(java匿名內(nèi)類可以訪問局部變量,但是只有當(dāng)這些內(nèi)類是final的時候才行)。

              第二個不同點不是定義形式的不同,但是也同樣重要。(The second difference is less of a defined formal difference, but is just as important, if not more so in practice)。支持閉包的語言允許你用很少的語法去定義一個閉包,盡管這點可能不是很重要的一點,但我相信這點是至關(guān)重要的-這是使得人們能很自然的使用閉包的關(guān)鍵點。看看Lisp,Smalltalk和Ruby,閉包遍布各處-比其它語言中類似的使用多很多。綁定局部變量是它的特點之一,但我想最大的原因是使用閉包的語法和符號非常簡單和清楚。

              一個很好的相關(guān)例子是從Smalltalk程序員到JAVA程序員,開始時很多人,包括我,試驗性的將在Smalltalk中使用閉包的地方在Java中使用匿名內(nèi)類來實現(xiàn)。但結(jié)果使得代碼變得混亂難看,所以我們不得不放棄。

             我在Ruby經(jīng)常使用閉包,但我不打算創(chuàng)建Proc對象,然后傳來傳去。大多數(shù)時間我用閉包來處理前面我提到的select等基于集合對象的方法。閉包另一個重要用途是'execute around method',比如處理一個文件:

          File.open(filename) {|f| doSomethingWithFile(f)}
          

             這里open方法打開一個文件,然后執(zhí)行給定的block,然后關(guān)閉它。這樣處理非常方便,尤其是對事務(wù)(要求commit或者rollback),或者其它的你需要在處理結(jié)束時候作一些收尾處理的事情。我在我的xml文檔轉(zhuǎn)換中廣泛使用這個優(yōu)點。

             閉包的這些用法顯然遠(yuǎn)不如用Lisp語言的人遇到的多,即使我,在使用沒有閉包支持的語言的時候,也會想念這些東西。閉包就像一些你第一眼見到覺得不怎么樣的東西,但你很快就會喜歡上它們。

          posted @ 2005-08-23 17:06 ivaneeo 閱讀(252) | 評論 (0)編輯 收藏

          如果你需要注釋來解釋一塊代碼做了什么,試試Extract Method(110);如果method已經(jīng)提煉出來,但還是需要注釋來解釋其行為,試試Rename Method(273);如果你需要注釋說明某些系統(tǒng)的需求規(guī)格,試試Introduce Assertion(267)。

          如果你不知道該做什么,這才是注釋的良好運用時機。除了用來記述將來的打算之外,注釋還可以用來標(biāo)記你并無十足把握的區(qū)域。你可以在注釋里寫下自己[為什么做某某事]。這類信息可以幫助將來的修改著,尤其是那些健忘的家伙。
          posted @ 2005-08-19 17:35 ivaneeo 閱讀(292) | 評論 (0)編輯 收藏

          subclass應(yīng)該繼承superclass的函數(shù)和數(shù)據(jù)。但如果它們不想或不需要繼承,又該怎么辦呢?它們得到所有禮物,卻只從中挑選幾樣來玩!

          按傳統(tǒng)說法,這就意味繼承系統(tǒng)設(shè)計錯誤。你需要為這個subclass新建一個兄弟(sibling class),再運用Push Down Method(328)和Push Down Field(329)把所有用不到的函數(shù)下推給那兄弟。這樣一來superclass就持有所有subclasses共享的東西。常常你會聽到這樣的建議:所有superclasses都應(yīng)該是抽象的(abstract)。

          既然使用[傳統(tǒng)說法]這個略帶貶義的詞,你就可以猜到,我們不建議你這么做,起碼不建議你每次都這么做。我們經(jīng)常利用subclassing手法來復(fù)用一些行為,并發(fā)現(xiàn)這可以很好地應(yīng)用于日常工作。這也是一種壞味道,我們不否認(rèn),但氣味通常并不強烈。所以我們說:如果Refused Bequest引起困惑和問題,請遵循傳統(tǒng)忠告。但不必認(rèn)為你每次都得那么做。十有八九這種壞味道很淡,不值得理睬。

          如果subclass復(fù)用了superclass的行為(實現(xiàn)),卻又不原意支持superclass得接口,Refused Bequest的壞味道就會變得濃烈。拒絕繼承superclass的實現(xiàn),這一點我們不介意;但如果拒絕繼承superclass的接口,我們不以為然。不過即使你不原意繼承接口,也不要胡亂修改繼承體系,你應(yīng)該運用Replace Inheritance with Delegation(352)來達(dá)到目的。
          posted @ 2005-08-19 17:26 ivaneeo 閱讀(386) | 評論 (0)編輯 收藏

          所謂Data Class是指:它們擁有一些值域(fields),以及用于訪問(讀寫)這些值域的函數(shù),除此之外一無長物。這樣的classes只是一種[不會說話的 數(shù)據(jù)容器],它們幾乎一定被其他classes過分細(xì)瑣地操控著。這些classes早期可能擁有public值域,果真如此你應(yīng)該在別人注意到它們之 前,立刻運用Encapsulate Field(206)將它們封裝起來。如果這些classes內(nèi)含容器類的值域(collection fields),你應(yīng)該檢查它們是不是得到了恰當(dāng)?shù)姆庋b;如果沒有,就運用Encapsulate Collection(208)把它們封裝起來。對于那些不該被其他classes修改的值域,請運用Remove Setting Method(300)。

          然后,找出這些[取值/設(shè)值]函數(shù)(getting and setting methods)被其他classes運用的地點。嘗試以Move Method(142)把那些調(diào)用行為搬移到Data Class來。如果無法搬移整個函數(shù),就運用Extract Method(110)產(chǎn)生一個可被搬移的函數(shù)。不久之后你就可以運用Hide Method(303)把這些[取值/設(shè)值]函數(shù)隱藏起來了。
          posted @ 2005-08-19 17:05 ivaneeo 閱讀(533) | 評論 (0)編輯 收藏

          幸好我們有兩個專門應(yīng)付這種情況的工具。如果你只想修改library classes內(nèi)的一兩個函數(shù),可以運用Introduce Foreign Method(162);如果想要添加一大堆額外行為,就得運用Introduce Local Extension(164)。
          posted @ 2005-08-19 16:50 ivaneeo 閱讀(269) | 評論 (0)編輯 收藏

          僅列出標(biāo)題
          共67頁: First 上一頁 49 50 51 52 53 54 55 56 57 下一頁 Last 
          主站蜘蛛池模板: 仙游县| 乐东| 蓬溪县| 沁阳市| 柳江县| 铁岭市| 松桃| 张家口市| 历史| 平南县| 荔波县| 山西省| 徐州市| 合江县| 靖远县| 扶余县| 章丘市| 安乡县| 江津市| 天长市| 宁武县| 临夏县| 阳曲县| 云阳县| 衡东县| 长春市| 车致| 英吉沙县| 铜鼓县| 特克斯县| 金塔县| 蓬安县| 桐柏县| 阿荣旗| 晋江市| 冕宁县| 怀安县| 景德镇市| 永川市| 宁国市| 金湖县|